mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-12 12:09:26 +00:00
Merge branch 'next' into dropdown-rework
This commit is contained in:
@@ -7,7 +7,20 @@ icon: slider
|
||||
---
|
||||
|
||||
```html {.example}
|
||||
<wa-slider></wa-slider>
|
||||
<wa-slider
|
||||
label="Number of cats"
|
||||
hint="Limit six per household"
|
||||
name="value"
|
||||
value="3"
|
||||
min="0"
|
||||
max="6"
|
||||
with-markers
|
||||
with-tooltip
|
||||
with-references
|
||||
>
|
||||
<span slot="reference">Less</span>
|
||||
<span slot="reference">More</span>
|
||||
</wa-slider>
|
||||
```
|
||||
|
||||
:::info
|
||||
@@ -18,7 +31,7 @@ This component works with standard `<form>` elements. Please refer to the sectio
|
||||
|
||||
### Labels
|
||||
|
||||
Use the `label` attribute to give the range an accessible label. For labels that contain HTML, use the `label` slot instead.
|
||||
Use the `label` attribute to give the slider an accessible label. For labels that contain HTML, use the `label` slot instead.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider label="Volume" min="0" max="100"></wa-slider>
|
||||
@@ -26,18 +39,233 @@ Use the `label` attribute to give the range an accessible label. For labels that
|
||||
|
||||
### Hint
|
||||
|
||||
Add descriptive hint to a range with the `hint` attribute. For hints that contain HTML, use the `hint` slot instead.
|
||||
Add descriptive hint to a slider with the `hint` attribute. For hints that contain HTML, use the `hint` slot instead.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider label="Volume" hint="Controls the volume of the current song." min="0" max="100"></wa-slider>
|
||||
```
|
||||
|
||||
### Min, Max, and Step
|
||||
### Showing tooltips
|
||||
|
||||
Use the `min` and `max` attributes to set the range's minimum and maximum values, respectively. The `step` attribute determines the value's interval when increasing and decreasing.
|
||||
Use the `with-tooltip` attribute to display a tooltip with the current value when the slider is focused or being dragged.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider min="0" max="10" step="1"></wa-slider>
|
||||
<wa-slider label="Quality" name="quality" min="0" max="100" value="50" with-tooltip></wa-slider>
|
||||
```
|
||||
|
||||
### Setting min, max, and step
|
||||
|
||||
Use the `min` and `max` attributes to define the slider's range, and the `step` attribute to control the increment between values.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider label="Between zero and one" min="0" max="1" step="0.1" value="0.5" with-tooltip></wa-slider>
|
||||
```
|
||||
|
||||
### Showing markers
|
||||
|
||||
Use the `with-markers` attribute to display visual indicators at each step increment. This works best with sliders that have a smaller range of values.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider label="Size" name="size" min="0" max="8" value="4" with-markers></wa-slider>
|
||||
```
|
||||
|
||||
### Adding references
|
||||
|
||||
Use the `with-references` attribute along with the `reference` slot to add contextual labels below the slider. References are automatically spaced using `space-between`, making them easy to align with the start, center, and end positions.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider label="Speed" name="speed" min="1" max="5" value="3" with-markers with-references>
|
||||
<span slot="reference">Slow</span>
|
||||
<span slot="reference">Medium</span>
|
||||
<span slot="reference">Fast</span>
|
||||
</wa-slider>
|
||||
```
|
||||
|
||||
:::info
|
||||
If you want to show a reference next to a specific marker, you can add `position: absolute` to it and set the `left`, `right`, `top`, or `bottom` property to a percentage that corresponds to the marker's position.
|
||||
:::
|
||||
|
||||
### Formatting the value
|
||||
|
||||
Customize how values are displayed in tooltips and announced to screen readers using the `valueFormatter` property. Set it to a function that accepts a number and returns a formatted string. The [`Intl.NumberFormat API`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) is particularly useful for this.
|
||||
|
||||
```html {.example}
|
||||
<!-- Percent -->
|
||||
<wa-slider
|
||||
id="slider__percent"
|
||||
label="Percentage"
|
||||
name="percentage"
|
||||
value="0.5"
|
||||
min="0"
|
||||
max="1"
|
||||
step=".01"
|
||||
with-tooltip
|
||||
></wa-slider
|
||||
><br />
|
||||
|
||||
<script>
|
||||
const slider = document.getElementById('slider__percent');
|
||||
const formatter = new Intl.NumberFormat('en-US', { style: 'percent' });
|
||||
|
||||
customElements.whenDefined('wa-slider').then(() => {
|
||||
slider.valueFormatter = value => formatter.format(value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Duration -->
|
||||
<wa-slider id="slider__duration" label="Duration" name="duration" value="12" min="0" max="24" with-tooltip></wa-slider
|
||||
><br />
|
||||
|
||||
<script>
|
||||
const slider = document.getElementById('slider__duration');
|
||||
const formatter = new Intl.NumberFormat('en-US', { style: 'unit', unit: 'hour', unitDisplay: 'long' });
|
||||
|
||||
customElements.whenDefined('wa-slider').then(() => {
|
||||
slider.valueFormatter = value => formatter.format(value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Currency -->
|
||||
<wa-slider id="slider__currency" label="Currency" name="currency" min="0" max="100" value="50" with-tooltip></wa-slider>
|
||||
|
||||
<script>
|
||||
const slider = document.getElementById('slider__currency');
|
||||
const formatter = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
currencyDisplay: 'symbol',
|
||||
maximumFractionDigits: 0,
|
||||
});
|
||||
|
||||
customElements.whenDefined('wa-slider').then(() => {
|
||||
slider.valueFormatter = value => formatter.format(value);
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
### Range selection
|
||||
|
||||
Use the `range` attribute to enable dual-thumb selection for choosing a range of values. Set the initial thumb positions with the `min-value` and `max-value` attributes.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider
|
||||
label="Price Range"
|
||||
hint="Select minimum and maximum price"
|
||||
name="price"
|
||||
range
|
||||
min="0"
|
||||
max="100"
|
||||
min-value="20"
|
||||
max-value="80"
|
||||
with-tooltip
|
||||
with-references
|
||||
id="slider__range"
|
||||
>
|
||||
<span slot="reference">$0</span>
|
||||
<span slot="reference">$50</span>
|
||||
<span slot="reference">$100</span>
|
||||
</wa-slider>
|
||||
|
||||
<script>
|
||||
const slider = document.getElementById('slider__range');
|
||||
const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 });
|
||||
|
||||
customElements.whenDefined('wa-slider').then(() => {
|
||||
slider.valueFormatter = value => formatter.format(value);
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
For range sliders, the `minValue` and `maxValue` properties represent the current positions of the thumbs. When the form is submitted, both values will be included as separate entries with the same name.
|
||||
|
||||
```ts
|
||||
const slider = document.querySelector('wa-slider[range]');
|
||||
|
||||
// Get the current values
|
||||
console.log(`Min value: ${slider.minValue}, Max value: ${slider.maxValue}`);
|
||||
|
||||
// Set the values programmatically
|
||||
slider.minValue = 30;
|
||||
slider.maxValue = 70;
|
||||
```
|
||||
|
||||
### Vertical Sliders
|
||||
|
||||
Set the `orientation` attribute to `vertical` to create a vertical slider. Vertical sliders automatically center themselves and fill the available vertical space.
|
||||
|
||||
```html {.example}
|
||||
<div style="display: flex; gap: 1rem;">
|
||||
<wa-slider orientation="vertical" label="Volume" name="volume" value="65" style="width: 80px"></wa-slider>
|
||||
|
||||
<wa-slider orientation="vertical" label="Bass" name="bass" value="50" style="width: 80px"></wa-slider>
|
||||
|
||||
<wa-slider orientation="vertical" label="Treble" name="treble" value="40" style="width: 80px"></wa-slider>
|
||||
</div>
|
||||
```
|
||||
|
||||
Range sliders can also be vertical.
|
||||
|
||||
```html {.example}
|
||||
<div style="height: 300px; display: flex; align-items: center; gap: 2rem;">
|
||||
<wa-slider
|
||||
label="Temperature Range"
|
||||
orientation="vertical"
|
||||
range
|
||||
min="0"
|
||||
max="100"
|
||||
min-value="30"
|
||||
max-value="70"
|
||||
with-tooltip
|
||||
tooltip-placement="right"
|
||||
id="slider__vertical-range"
|
||||
>
|
||||
</wa-slider>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const slider = document.getElementById('slider__vertical-range');
|
||||
slider.valueFormatter = value => {
|
||||
return new Intl.NumberFormat('en', {
|
||||
style: 'unit',
|
||||
unit: 'fahrenheit',
|
||||
unitDisplay: 'short',
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 1,
|
||||
}).format(value);
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
### Size
|
||||
|
||||
Control the slider's size using the `size` attribute. Valid options include `small`, `medium`, and `large`.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider size="small" value="50" label="Small"></wa-slider><br />
|
||||
<wa-slider size="medium" value="50" label="Medium"></wa-slider><br />
|
||||
<wa-slider size="large" value="50" label="Large"></wa-slider>
|
||||
```
|
||||
|
||||
### Indicator Offset
|
||||
|
||||
By default, the filled indicator extends from the minimum value to the current position. Use the `indicator-offset` attribute to change the starting point of this visual indicator.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider
|
||||
label="Cat playfulness"
|
||||
hint="Energy level during playtime"
|
||||
name="value"
|
||||
value="0"
|
||||
min="-5"
|
||||
max="5"
|
||||
indicator-offset="0"
|
||||
with-markers
|
||||
with-tooltip
|
||||
with-references
|
||||
>
|
||||
<span slot="reference">Lazy</span>
|
||||
<span slot="reference">Zoomies</span>
|
||||
</wa-slider>
|
||||
```
|
||||
|
||||
### Disabled
|
||||
@@ -45,74 +273,17 @@ Use the `min` and `max` attributes to set the range's minimum and maximum values
|
||||
Use the `disabled` attribute to disable a slider.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider disabled></wa-slider>
|
||||
<wa-slider label="Disabled" value="50" disabled></wa-slider>
|
||||
```
|
||||
|
||||
### Tooltip Placement
|
||||
### Required
|
||||
|
||||
By default, the tooltip is shown on top. Set `tooltip` to `bottom` to show it below the slider.
|
||||
Mark a slider as required using the `required` attribute. Users must interact with required sliders before the form can be submitted.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider tooltip="bottom"></wa-slider>
|
||||
```
|
||||
|
||||
### Disable the Tooltip
|
||||
|
||||
To disable the tooltip, set `tooltip` to `none`.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider tooltip="none"></wa-slider>
|
||||
```
|
||||
|
||||
### Custom Track Colors
|
||||
|
||||
You can customize the active and inactive portions of the track using the `--track-color-active` and `--track-color-inactive` custom properties.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider
|
||||
style="
|
||||
--track-color-active: var(--wa-color-brand-fill-loud);
|
||||
--track-color-inactive: var(--wa-color-brand-fill-normal);
|
||||
"
|
||||
></wa-slider>
|
||||
```
|
||||
|
||||
### Custom Track Offset
|
||||
|
||||
You can customize the initial offset of the active track using the `--track-active-offset` custom property.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider
|
||||
min="-100"
|
||||
max="100"
|
||||
style="
|
||||
--track-color-active: var(--wa-color-brand-fill-loud);
|
||||
--track-color-inactive: var(--wa-color-brand-fill-normal);
|
||||
--track-active-offset: 50%;
|
||||
"
|
||||
></wa-slider>
|
||||
```
|
||||
|
||||
### Custom Tooltip Formatter
|
||||
|
||||
You can change the tooltip's content by setting the `tooltipFormatter` property to a function that accepts the range's value as an argument.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider min="0" max="100" step="1" class="range-with-custom-formatter"></wa-slider>
|
||||
|
||||
<script>
|
||||
const range = document.querySelector('.range-with-custom-formatter');
|
||||
range.tooltipFormatter = value => `Total - ${value}%`;
|
||||
</script>
|
||||
```
|
||||
|
||||
### Right-to-Left languages
|
||||
|
||||
The component adapts to right-to-left (RTL) languages as you would expect.
|
||||
|
||||
```html {.example}
|
||||
<wa-slider dir="rtl"
|
||||
label="مقدار"
|
||||
hint="التحكم في مستوى صوت الأغنية الحالية."
|
||||
style="--track-color-active: var(--wa-color-brand-fill-loud)" value="10"></wa-slider>
|
||||
```
|
||||
<form action="about:blank" target="_blank" method="get">
|
||||
<wa-slider name="slide" label="Required slider" min="0" max="10" required></wa-slider>
|
||||
<br />
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
```
|
||||
@@ -38,6 +38,16 @@ During the alpha period, things might break! We take breaking changes very serio
|
||||
- Added convenience tokens for `--wa-font-size-smaller` and `--wa-font-size-larger`
|
||||
- Updated components to use relative `em` values for internal padding and margin wherever appropriate
|
||||
- 🚨 BREAKING: removed the `hint` property and slot from `<wa-radio>`; please apply hints directly to `<wa-radio-group>` instead
|
||||
- 🚨 BREAKING: redesigned `<wa-slider>` with extensive new functionality
|
||||
- Added support for range sliders with dual thumbs using the `range` attribute
|
||||
- Added vertical orientation support with `orientation="vertical"`
|
||||
- Added visual markers at each step with `with-markers`
|
||||
- Added contextual reference labels with `with-references` and the `reference` slot
|
||||
- Added tooltips showing current values with `with-tooltip`
|
||||
- Added customizable indicator offset with `indicator-offset` attribute
|
||||
- Added value formatting support with the `valueFormatter` property
|
||||
- Improved the styling API to be consistent and more powerful (no more browser-specific selectors and pseudo elements to style)
|
||||
- Updated to use consistent `with-*` attribute naming pattern
|
||||
- 🚨 BREAKING: removed `<wa-icon-button>`; use `<wa-button><wa-icon name="..." label="..."></wa-icon></wa-button>` instead
|
||||
- 🚨 BREAKING: completely reworked `<wa-dropdown>` to be easier to use
|
||||
- Added `<wa-dropdown-item>`, greatly simplifying the dropdown's markup structure
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { live } from 'lit/directives/live.js';
|
||||
import { WaClearEvent } from '../../events/clear.js';
|
||||
import { HasSlotController } from '../../internal/slot.js';
|
||||
import { submitOnEnter } from '../../internal/submit-on-enter.js';
|
||||
import { MirrorValidator } from '../../internal/validators/mirror-validator.js';
|
||||
import { watch } from '../../internal/watch.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
|
||||
@@ -12,7 +13,6 @@ import formControlStyles from '../../styles/component/form-control.css';
|
||||
import appearanceStyles from '../../styles/utilities/appearance.css';
|
||||
import sizeStyles from '../../styles/utilities/size.css';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import type WaButton from '../button/button.js';
|
||||
import '../icon/icon.js';
|
||||
import styles from './input.css';
|
||||
|
||||
@@ -245,51 +245,7 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent) {
|
||||
const hasModifier = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
|
||||
|
||||
// Pressing enter when focused on an input should submit the form like a native input, but we wait a tick before
|
||||
// submitting to allow users to cancel the keydown event if they need to
|
||||
if (event.key === 'Enter' && !hasModifier) {
|
||||
setTimeout(() => {
|
||||
//
|
||||
// When using an Input Method Editor (IME), pressing enter will cause the form to submit unexpectedly. One way
|
||||
// to check for this is to look at event.isComposing, which will be true when the IME is open.
|
||||
//
|
||||
// See https://github.com/shoelace-style/shoelace/pull/988
|
||||
//
|
||||
if (!event.defaultPrevented && !event.isComposing) {
|
||||
const form = this.getForm();
|
||||
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formElements = [...form.elements];
|
||||
|
||||
// If we're the only formElement, we submit like a native input.
|
||||
if (formElements.length === 1) {
|
||||
form.requestSubmit(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const button = formElements.find(
|
||||
(el: HTMLButtonElement) => el.type === 'submit' && !el.matches(':disabled'),
|
||||
) as undefined | HTMLButtonElement | WaButton;
|
||||
|
||||
// No button found, don't submit.
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (button.tagName.toLowerCase() === 'button') {
|
||||
form.requestSubmit(button);
|
||||
} else {
|
||||
// requestSubmit() wont work with `<wa-button>`
|
||||
button.click();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
submitOnEnter(event, this);
|
||||
}
|
||||
|
||||
private handlePasswordToggle() {
|
||||
|
||||
@@ -1,214 +1,227 @@
|
||||
:host {
|
||||
--thumb-color: var(--wa-form-control-activated-color);
|
||||
--thumb-gap: calc(var(--thumb-size) * 0.125);
|
||||
--thumb-shadow: initial;
|
||||
--thumb-size: calc(1em * var(--wa-form-control-value-line-height));
|
||||
|
||||
--track-color-active: var(--wa-color-neutral-fill-normal);
|
||||
--track-color-inactive: var(--wa-color-neutral-fill-normal);
|
||||
--track-active-offset: 0%;
|
||||
--track-height: calc(var(--thumb-size) * 0.25);
|
||||
--tooltip-offset: calc(var(--wa-tooltip-arrow-size) * 1.375);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
min-height: max(var(--thumb-size), var(--track-height));
|
||||
--track-size: 0.5em;
|
||||
--thumb-width: 1.4em;
|
||||
--thumb-height: 1.4em;
|
||||
--marker-width: 0.1875em;
|
||||
--marker-height: 0.1875em;
|
||||
}
|
||||
|
||||
input[type='range'] {
|
||||
--percent: 0%;
|
||||
-webkit-appearance: none;
|
||||
border-radius: calc(var(--track-height) / 2);
|
||||
width: 100%;
|
||||
height: var(--track-height);
|
||||
font-size: inherit;
|
||||
line-height: var(--wa-form-control-height);
|
||||
vertical-align: middle;
|
||||
margin: 0;
|
||||
--dir: right;
|
||||
:host([orientation='vertical']) {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
background-image: linear-gradient(
|
||||
to var(--dir),
|
||||
var(--track-color-inactive) min(var(--percent), var(--track-active-offset)),
|
||||
var(--track-color-active) min(var(--percent), var(--track-active-offset)),
|
||||
var(--track-color-active) max(var(--percent), var(--track-active-offset)),
|
||||
var(--track-color-inactive) max(var(--percent), var(--track-active-offset))
|
||||
);
|
||||
#label:has(~ .vertical) {
|
||||
display: block;
|
||||
order: 2;
|
||||
max-width: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#description:has(~ .vertical) {
|
||||
order: 3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Add extra space between slider and label, when present */
|
||||
#label:has(*:not(:empty)) ~ #slider {
|
||||
&.horizontal {
|
||||
margin-block-start: 0.5em;
|
||||
}
|
||||
&.vertical {
|
||||
margin-block-end: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
#slider {
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-visible:not(.disabled) #thumb,
|
||||
&:focus-visible:not(.disabled) #thumb-min,
|
||||
&:focus-visible:not(.disabled) #thumb-max {
|
||||
outline: var(--wa-focus-ring);
|
||||
/* intentionally no offset due to border */
|
||||
}
|
||||
}
|
||||
|
||||
#track {
|
||||
position: relative;
|
||||
border-radius: 9999px;
|
||||
background: var(--wa-color-neutral-fill-normal);
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
/* Orientation */
|
||||
.horizontal #track {
|
||||
height: var(--track-size);
|
||||
}
|
||||
|
||||
.vertical #track {
|
||||
order: 1;
|
||||
width: var(--track-size);
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
/* Disabled */
|
||||
.disabled #track {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Indicator */
|
||||
#indicator {
|
||||
position: absolute;
|
||||
border-radius: inherit;
|
||||
background-color: var(--wa-form-control-activated-color);
|
||||
|
||||
&:dir(ltr) {
|
||||
right: calc(100% - max(var(--start), var(--end)));
|
||||
left: min(var(--start), var(--end));
|
||||
}
|
||||
|
||||
&:dir(rtl) {
|
||||
--dir: left;
|
||||
}
|
||||
|
||||
&::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
height: var(--track-height);
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
width: var(--thumb-size);
|
||||
height: var(--thumb-size);
|
||||
border-radius: 50%;
|
||||
background-color: var(--thumb-color);
|
||||
box-shadow:
|
||||
var(--thumb-shadow, 0 0 transparent),
|
||||
0 0 0 var(--thumb-gap) var(--wa-color-surface-default);
|
||||
-webkit-appearance: none;
|
||||
margin-top: calc(var(--thumb-size) / -2 + var(--track-height) / 2);
|
||||
transition: var(--wa-transition-fast);
|
||||
transition-property: width, height;
|
||||
}
|
||||
|
||||
&:enabled {
|
||||
&:focus-visible::-webkit-slider-thumb {
|
||||
outline: var(--wa-focus-ring);
|
||||
outline-offset: var(--wa-focus-ring-offset);
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::-webkit-slider-thumb:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
&::-moz-focus-outer {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&::-moz-range-progress {
|
||||
background-color: var(--track-color-active);
|
||||
border-radius: 3px;
|
||||
height: var(--track-height);
|
||||
}
|
||||
|
||||
&::-moz-range-track {
|
||||
width: 100%;
|
||||
height: var(--track-height);
|
||||
background-color: var(--track-color-inactive);
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
height: var(--thumb-size);
|
||||
width: var(--thumb-size);
|
||||
border-radius: 50%;
|
||||
background-color: var(--thumb-color);
|
||||
box-shadow:
|
||||
var(--thumb-shadow, 0 0 transparent),
|
||||
0 0 0 var(--thumb-gap) var(--wa-color-surface-default);
|
||||
transition-property: background-color, border-color, box-shadow, color;
|
||||
transition-duration: var(--wa-transition-normal);
|
||||
transition-timing-function: var(--wa-transition-easing);
|
||||
}
|
||||
|
||||
&:enabled {
|
||||
&:focus-visible::-moz-range-thumb {
|
||||
outline: var(--wa-focus-ring);
|
||||
outline-offset: var(--wa-focus-ring-offset);
|
||||
}
|
||||
|
||||
&::-moz-range-thumb {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&::-moz-range-thumb:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
right: min(var(--start), var(--end));
|
||||
left: calc(100% - max(var(--start), var(--end)));
|
||||
}
|
||||
}
|
||||
|
||||
/* States */
|
||||
/* nesting these styles yields broken results in Safari */
|
||||
input[type='range']:focus {
|
||||
outline: none;
|
||||
.horizontal #indicator {
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
:host :has(:disabled) input[type='range'] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
|
||||
&::-moz-range-thumb,
|
||||
&::-webkit-slider-thumb {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.vertical #indicator {
|
||||
top: calc(100% - var(--end));
|
||||
bottom: var(--start);
|
||||
left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Tooltip output */
|
||||
.tooltip {
|
||||
/* Thumbs */
|
||||
#thumb,
|
||||
#thumb-min,
|
||||
#thumb-max {
|
||||
z-index: 3;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
inset-inline-start: 0;
|
||||
width: var(--thumb-width);
|
||||
height: var(--thumb-height);
|
||||
border: solid 0.125em var(--wa-color-surface-default);
|
||||
border-radius: 50%;
|
||||
background-color: var(--wa-form-control-activated-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
inset-block-end: calc(50% + (var(--thumb-size) / 2) + var(--tooltip-offset));
|
||||
border-radius: var(--wa-tooltip-border-radius);
|
||||
background-color: var(--wa-tooltip-background-color);
|
||||
font-family: inherit;
|
||||
font-size: var(--wa-tooltip-font-size);
|
||||
line-height: var(--wa-tooltip-line-height);
|
||||
color: var(--wa-tooltip-content-color);
|
||||
opacity: 0;
|
||||
padding: 0.25em 0.5em;
|
||||
transition: var(--wa-transition-normal) opacity;
|
||||
.disabled #thumb,
|
||||
.disabled #thumb-min,
|
||||
.disabled #thumb-max {
|
||||
cursor: inherit;
|
||||
}
|
||||
|
||||
.horizontal #thumb,
|
||||
.horizontal #thumb-min,
|
||||
.horizontal #thumb-max {
|
||||
top: calc(50% - var(--thumb-height) / 2);
|
||||
|
||||
&:dir(ltr) {
|
||||
right: auto;
|
||||
left: calc(var(--position) - var(--thumb-width) / 2);
|
||||
}
|
||||
|
||||
&:dir(rtl) {
|
||||
right: calc(var(--position) - var(--thumb-width) / 2);
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.vertical #thumb,
|
||||
.vertical #thumb-min,
|
||||
.vertical #thumb-max {
|
||||
bottom: calc(var(--position) - var(--thumb-height) / 2);
|
||||
left: calc(50% - var(--thumb-width) / 2);
|
||||
}
|
||||
|
||||
/* Range-specific thumb styles */
|
||||
:host([range]) {
|
||||
#thumb-min:focus-visible,
|
||||
#thumb-max:focus-visible {
|
||||
z-index: 4; /* Ensure focused thumb appears on top */
|
||||
outline: var(--wa-focus-ring);
|
||||
/* intentionally no offset due to border */
|
||||
}
|
||||
}
|
||||
|
||||
/* Markers */
|
||||
#markers {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
inset-inline-start: 50%;
|
||||
inset-block-start: 100%;
|
||||
translate: calc(-1 * var(--wa-tooltip-arrow-size));
|
||||
border-inline: var(--wa-tooltip-arrow-size) solid transparent;
|
||||
border-block-start: var(--border-block);
|
||||
.marker {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
width: var(--marker-width);
|
||||
height: var(--marker-height);
|
||||
border-radius: 50%;
|
||||
background-color: var(--wa-color-surface-default);
|
||||
}
|
||||
|
||||
.marker:first-of-type,
|
||||
.marker:last-of-type {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.horizontal .marker {
|
||||
top: calc(50% - var(--marker-height) / 2);
|
||||
left: calc(var(--position) - var(--marker-width) / 2);
|
||||
}
|
||||
|
||||
.vertical .marker {
|
||||
top: calc(var(--position) - var(--marker-height) / 2);
|
||||
left: calc(50% - var(--marker-width) / 2);
|
||||
}
|
||||
|
||||
/* Marker labels */
|
||||
#references {
|
||||
position: relative;
|
||||
|
||||
slot {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:dir(rtl)::after {
|
||||
translate: var(--wa-tooltip-arrow-size);
|
||||
::slotted(*) {
|
||||
color: var(--wa-color-text-quiet);
|
||||
font-size: 0.875em;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
#references {
|
||||
margin-block-start: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.vertical {
|
||||
display: flex;
|
||||
margin-inline: auto;
|
||||
|
||||
#track {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
&.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
#references {
|
||||
order: 2;
|
||||
width: min-content;
|
||||
margin-inline-start: 0.75em;
|
||||
|
||||
--inset-block: calc(50% + (var(--thumb-size) / 2) + var(--tooltip-offset));
|
||||
--border-block: var(--wa-tooltip-arrow-size) solid var(--wa-tooltip-background-color);
|
||||
|
||||
@media (forced-colors: active) {
|
||||
border: solid 1px transparent;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
slot {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* RTL tooltip positioning */
|
||||
:host(:dir(rtl)) .tooltip {
|
||||
inset-inline-start: auto;
|
||||
inset-inline-end: 0;
|
||||
}
|
||||
|
||||
/* Tooltip on bottom */
|
||||
:host([tooltip='bottom']) .tooltip {
|
||||
inset-block-end: auto;
|
||||
inset-block-start: calc(50% + (var(--thumb-size) / 2) + var(--tooltip-offset));
|
||||
|
||||
&::after {
|
||||
border-block-end: var(--border-block);
|
||||
inset-block-start: auto;
|
||||
inset-block-end: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Bottom tooltip RTL fix */
|
||||
:host([tooltip='bottom']:dir(rtl)) .tooltip {
|
||||
inset-inline-start: auto;
|
||||
inset-inline-end: 0;
|
||||
.vertical #references slot {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@@ -21,9 +21,8 @@ describe('<wa-slider>', () => {
|
||||
it('default properties', async () => {
|
||||
const el = await fixture<WaSlider>(html` <wa-slider></wa-slider> `);
|
||||
|
||||
expect(el.name).to.equal('');
|
||||
expect(el.name).to.equal(null);
|
||||
expect(el.value).to.equal(0);
|
||||
expect(el.title).to.equal('');
|
||||
expect(el.label).to.equal('');
|
||||
expect(el.hint).to.equal('');
|
||||
expect(el.disabled).to.be.false;
|
||||
@@ -31,22 +30,16 @@ describe('<wa-slider>', () => {
|
||||
expect(el.min).to.equal(0);
|
||||
expect(el.max).to.equal(100);
|
||||
expect(el.step).to.equal(1);
|
||||
expect(el.tooltip).to.equal('top');
|
||||
expect(el.tooltipPlacement).to.equal('top');
|
||||
expect(el.defaultValue).to.equal(0);
|
||||
});
|
||||
|
||||
it('should have title if title attribute is set', async () => {
|
||||
const el = await fixture<WaSlider>(html` <wa-slider title="Test"></wa-slider> `);
|
||||
const input = el.shadowRoot!.querySelector('input')!;
|
||||
|
||||
expect(input.title).to.equal('Test');
|
||||
});
|
||||
|
||||
it('should be disabled with the disabled attribute', async () => {
|
||||
const el = await fixture<WaSlider>(html` <wa-slider disabled></wa-slider> `);
|
||||
const input = el.shadowRoot!.querySelector<HTMLInputElement>('.control')!;
|
||||
const input = el.shadowRoot!.querySelector<HTMLElement>("[role='slider']")!;
|
||||
|
||||
expect(input.disabled).to.be.true;
|
||||
expect(el.matches(':disabled')).to.be.true;
|
||||
expect(input.getAttribute('aria-disabled')).to.equal('true');
|
||||
});
|
||||
|
||||
describe('when the value changes', () => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -43,3 +43,132 @@ export function drag(container: HTMLElement, options?: Partial<DragOptions>) {
|
||||
move(options.initialEvent);
|
||||
}
|
||||
}
|
||||
|
||||
const supportsTouch = typeof window !== 'undefined' && 'ontouchstart' in window;
|
||||
|
||||
/**
|
||||
* Attaches the necessary events to make an element draggable.
|
||||
*
|
||||
* This by itself will not make the element draggable, but it provides the events and callbacks necessary to facilitate
|
||||
* dragging. Use the `clientX` and `clientY` arguments of each callback to update the UI as desired when dragging.
|
||||
*
|
||||
* Drag functionality will be enabled as soon as the constructor is called. A `start()` and `stop()` method can be used
|
||||
* to start and stop it, if needed.
|
||||
*
|
||||
* @usage
|
||||
*
|
||||
* const draggable = new DraggableElement(element, {
|
||||
* start: (clientX, clientY) => { ... },
|
||||
* move: (clientX, clientY) => { ... },
|
||||
* stop: (clientX, clientY) => { ... }
|
||||
* });
|
||||
*/
|
||||
export class DraggableElement {
|
||||
private element: Element;
|
||||
private isActive = false;
|
||||
private isDragging = false;
|
||||
private options: DraggableElementOptions;
|
||||
|
||||
constructor(el: Element, options: Partial<DraggableElementOptions>) {
|
||||
this.element = el;
|
||||
this.options = {
|
||||
start: () => undefined,
|
||||
stop: () => undefined,
|
||||
move: () => undefined,
|
||||
...options,
|
||||
};
|
||||
|
||||
this.start();
|
||||
}
|
||||
|
||||
private handleDragStart = (event: PointerEvent | TouchEvent) => {
|
||||
const clientX = supportsTouch && 'touches' in event ? event.touches[0].clientX : (event as PointerEvent).clientX;
|
||||
const clientY = supportsTouch && 'touches' in event ? event.touches[0].clientY : (event as PointerEvent).clientY;
|
||||
|
||||
// Prevent scrolling while dragging
|
||||
event.preventDefault();
|
||||
|
||||
if (
|
||||
this.isDragging ||
|
||||
// Prevent right-clicks from triggering drags
|
||||
(!supportsTouch && (event as PointerEvent).buttons > 1)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDragging = true;
|
||||
|
||||
document.addEventListener('pointerup', this.handleDragStop);
|
||||
document.addEventListener('pointermove', this.handleDragMove);
|
||||
document.addEventListener('touchend', this.handleDragStop);
|
||||
document.addEventListener('touchmove', this.handleDragMove);
|
||||
this.options.start(clientX, clientY);
|
||||
};
|
||||
|
||||
private handleDragStop = (event: PointerEvent | TouchEvent) => {
|
||||
const clientX = supportsTouch && 'touches' in event ? event.touches[0].clientX : (event as PointerEvent).clientX;
|
||||
const clientY = supportsTouch && 'touches' in event ? event.touches[0].clientY : (event as PointerEvent).clientY;
|
||||
|
||||
this.isDragging = false;
|
||||
document.removeEventListener('pointerup', this.handleDragStop);
|
||||
document.removeEventListener('pointermove', this.handleDragMove);
|
||||
document.removeEventListener('touchend', this.handleDragStop);
|
||||
document.removeEventListener('touchmove', this.handleDragMove);
|
||||
this.options.stop(clientX, clientY);
|
||||
};
|
||||
|
||||
private handleDragMove = (event: PointerEvent | TouchEvent) => {
|
||||
const clientX = supportsTouch && 'touches' in event ? event.touches[0].clientX : (event as PointerEvent).clientX;
|
||||
const clientY = supportsTouch && 'touches' in event ? event.touches[0].clientY : (event as PointerEvent).clientY;
|
||||
|
||||
// Prevent text selection while dragging
|
||||
window.getSelection()?.removeAllRanges();
|
||||
|
||||
this.options.move(clientX, clientY);
|
||||
};
|
||||
|
||||
/** Start listening to drags. */
|
||||
public start() {
|
||||
if (!this.isActive) {
|
||||
this.element.addEventListener('pointerdown', this.handleDragStart);
|
||||
if (supportsTouch) {
|
||||
this.element.addEventListener('touchstart', this.handleDragStart);
|
||||
}
|
||||
this.isActive = true;
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop listening to drags. */
|
||||
public stop() {
|
||||
document.removeEventListener('pointerup', this.handleDragStop);
|
||||
document.removeEventListener('pointermove', this.handleDragMove);
|
||||
document.removeEventListener('touchend', this.handleDragStop);
|
||||
document.removeEventListener('touchmove', this.handleDragMove);
|
||||
this.element.removeEventListener('pointerdown', this.handleDragStart);
|
||||
if (supportsTouch) {
|
||||
this.element.removeEventListener('touchstart', this.handleDragStart);
|
||||
}
|
||||
this.isActive = false;
|
||||
this.isDragging = false;
|
||||
}
|
||||
|
||||
/** Starts or stops the drag listeners. */
|
||||
public toggle(isActive?: boolean) {
|
||||
const isGoingToBeActive = isActive !== undefined ? isActive : !this.isActive;
|
||||
|
||||
if (isGoingToBeActive) {
|
||||
this.start();
|
||||
} else {
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface DraggableElementOptions {
|
||||
/** Runs when dragging starts. */
|
||||
start: (clientX: number, clientY: number) => void;
|
||||
/** Runs as the user is dragging. This may execute often, so avoid expensive operations. */
|
||||
move: (clientX: number, clientY: number) => void;
|
||||
/** Runs when dragging ends. */
|
||||
stop: (clientX: number, clientY: number) => void;
|
||||
}
|
||||
|
||||
64
packages/webawesome/src/internal/submit-on-enter.ts
Normal file
64
packages/webawesome/src/internal/submit-on-enter.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type WaButton from '../components/button/button.js';
|
||||
import type { WebAwesomeFormAssociatedElement } from './webawesome-form-associated-element.js';
|
||||
|
||||
export function submitOnEnter<T extends HTMLElement>(event: KeyboardEvent, el: T) {
|
||||
const hasModifier = event.metaKey || event.ctrlKey || event.shiftKey || event.altKey;
|
||||
|
||||
// Pressing enter when focused on an input should submit the form like a native input, but we wait a tick before
|
||||
// submitting to allow users to cancel the keydown event if they need to
|
||||
if (event.key === 'Enter' && !hasModifier) {
|
||||
// setTimeout in case the event is caught higher up in the tree and defaultPrevented
|
||||
setTimeout(() => {
|
||||
//
|
||||
// When using an Input Method Editor (IME), pressing enter will cause the form to submit unexpectedly. One way
|
||||
// to check for this is to look at event.isComposing, which will be true when the IME is open.
|
||||
//
|
||||
// See https://github.com/shoelace-style/shoelace/pull/988
|
||||
//
|
||||
if (!event.defaultPrevented && !event.isComposing) {
|
||||
submitForm(el);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function submitForm(el: HTMLElement | WebAwesomeFormAssociatedElement) {
|
||||
let form: HTMLFormElement | null = null;
|
||||
|
||||
if ('form' in el) {
|
||||
form = el.form as HTMLFormElement | null;
|
||||
}
|
||||
|
||||
if (!form && 'getForm' in el) {
|
||||
form = el.getForm();
|
||||
}
|
||||
|
||||
if (!form) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formElements = [...form.elements];
|
||||
|
||||
// If we're the only formElement, we submit like a native input.
|
||||
if (formElements.length === 1) {
|
||||
form.requestSubmit(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const button = formElements.find((el: HTMLButtonElement) => el.type === 'submit' && !el.matches(':disabled')) as
|
||||
| undefined
|
||||
| HTMLButtonElement
|
||||
| WaButton;
|
||||
|
||||
// No button found, don't submit.
|
||||
if (!button) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (['input', 'button'].includes(button.localName)) {
|
||||
form.requestSubmit(button);
|
||||
} else {
|
||||
// requestSubmit() wont work with `<wa-button>`, so trigger a manual click.
|
||||
button.click();
|
||||
}
|
||||
}
|
||||
@@ -162,6 +162,7 @@ function runAllValidityTests(
|
||||
const form = await fixture(html`<form id="${formId}"></form>`);
|
||||
const control = await createControl();
|
||||
expect(control.getForm()).to.equal(null);
|
||||
// control.setAttribute("form", 'test-form');
|
||||
control.form = 'test-form';
|
||||
await control.updateComplete;
|
||||
expect(control.getForm()).to.equal(form);
|
||||
|
||||
123
packages/webawesome/src/internal/validators/slider-validator.ts
Normal file
123
packages/webawesome/src/internal/validators/slider-validator.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import type WaSlider from '../../components/slider/slider.js';
|
||||
import type { Validator } from '../webawesome-form-associated-element.js';
|
||||
|
||||
/**
|
||||
* Comprehensive validator for sliders that handles required, range, and step validation
|
||||
*/
|
||||
export const SliderValidator = (): Validator<WaSlider> => {
|
||||
// Create a native range input to get localized validation messages
|
||||
const nativeRequiredRange = Object.assign(document.createElement('input'), {
|
||||
type: 'range',
|
||||
required: true,
|
||||
});
|
||||
|
||||
return {
|
||||
observedAttributes: ['required', 'min', 'max', 'step'],
|
||||
checkValidity(element) {
|
||||
const validity: ReturnType<Validator['checkValidity']> = {
|
||||
message: '',
|
||||
isValid: true,
|
||||
invalidKeys: [],
|
||||
};
|
||||
|
||||
// Create native range input to get localized validation messages
|
||||
const createNativeRange = (value: number, min: number, max: number, step: number) => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'range';
|
||||
input.min = String(min);
|
||||
input.max = String(max);
|
||||
input.step = String(step);
|
||||
input.value = String(value);
|
||||
|
||||
// Trigger validation
|
||||
input.checkValidity();
|
||||
return input.validationMessage;
|
||||
};
|
||||
|
||||
// Check required validation first
|
||||
if (element.required && !element.hasInteracted) {
|
||||
validity.isValid = false;
|
||||
validity.invalidKeys.push('valueMissing');
|
||||
validity.message = nativeRequiredRange.validationMessage || 'Please fill out this field.';
|
||||
return validity;
|
||||
}
|
||||
|
||||
// For range sliders, validate both values
|
||||
if (element.isRange) {
|
||||
const minValue = element.minValue;
|
||||
const maxValue = element.maxValue;
|
||||
|
||||
// Check range underflow for min value
|
||||
if (minValue < element.min) {
|
||||
validity.isValid = false;
|
||||
validity.invalidKeys.push('rangeUnderflow');
|
||||
validity.message =
|
||||
createNativeRange(minValue, element.min, element.max, element.step) ||
|
||||
`Value must be greater than or equal to ${element.min}.`;
|
||||
return validity;
|
||||
}
|
||||
|
||||
// Check range overflow for max value
|
||||
if (maxValue > element.max) {
|
||||
validity.isValid = false;
|
||||
validity.invalidKeys.push('rangeOverflow');
|
||||
validity.message =
|
||||
createNativeRange(maxValue, element.min, element.max, element.step) ||
|
||||
`Value must be less than or equal to ${element.max}.`;
|
||||
return validity;
|
||||
}
|
||||
|
||||
// Check step mismatch
|
||||
if (element.step && element.step !== 1) {
|
||||
const minStepMismatch = (minValue - element.min) % element.step !== 0;
|
||||
const maxStepMismatch = (maxValue - element.min) % element.step !== 0;
|
||||
|
||||
if (minStepMismatch || maxStepMismatch) {
|
||||
validity.isValid = false;
|
||||
validity.invalidKeys.push('stepMismatch');
|
||||
const testValue = minStepMismatch ? minValue : maxValue;
|
||||
validity.message =
|
||||
createNativeRange(testValue, element.min, element.max, element.step) ||
|
||||
`Value must be a multiple of ${element.step}.`;
|
||||
return validity;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Single value validation
|
||||
const value = element.value;
|
||||
|
||||
// Check range underflow
|
||||
if (value < element.min) {
|
||||
validity.isValid = false;
|
||||
validity.invalidKeys.push('rangeUnderflow');
|
||||
validity.message =
|
||||
createNativeRange(value, element.min, element.max, element.step) ||
|
||||
`Value must be greater than or equal to ${element.min}.`;
|
||||
return validity;
|
||||
}
|
||||
|
||||
// Check range overflow
|
||||
if (value > element.max) {
|
||||
validity.isValid = false;
|
||||
validity.invalidKeys.push('rangeOverflow');
|
||||
validity.message =
|
||||
createNativeRange(value, element.min, element.max, element.step) ||
|
||||
`Value must be less than or equal to ${element.max}.`;
|
||||
return validity;
|
||||
}
|
||||
|
||||
// Check step mismatch
|
||||
if (element.step && element.step !== 1 && (value - element.min) % element.step !== 0) {
|
||||
validity.isValid = false;
|
||||
validity.invalidKeys.push('stepMismatch');
|
||||
validity.message =
|
||||
createNativeRange(value, element.min, element.max, element.step) ||
|
||||
`Value must be a multiple of ${element.step}.`;
|
||||
return validity;
|
||||
}
|
||||
}
|
||||
|
||||
return validity;
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -48,10 +48,10 @@
|
||||
--box-shadow: inset var(--wa-shadow-s);
|
||||
}
|
||||
}
|
||||
input[type='range'],
|
||||
wa-slider,
|
||||
wa-switch {
|
||||
--thumb-shadow: var(--wa-theme-active-shadow-pop-out);
|
||||
|
||||
wa-slider::part(thumb),
|
||||
wa-switch::part(thumb) {
|
||||
box-shadow: var(--wa-theme-active-shadow-pop-out);
|
||||
}
|
||||
|
||||
wa-progress-bar {
|
||||
|
||||
@@ -45,7 +45,8 @@
|
||||
wa-carousel::part(pagination-item),
|
||||
wa-comparison::part(handle),
|
||||
wa-progress-bar::part(base),
|
||||
wa-slider::part(base),
|
||||
wa-slider::part(track),
|
||||
wa-slider::part(thumb),
|
||||
input[type='range'],
|
||||
wa-switch::part(control),
|
||||
wa-switch::part(thumb) {
|
||||
|
||||
@@ -97,10 +97,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
input[type='range'],
|
||||
wa-slider,
|
||||
wa-switch {
|
||||
--thumb-shadow:
|
||||
wa-slider::part(thumb),
|
||||
wa-switch::part(thumb) {
|
||||
box-shadow:
|
||||
var(--wa-theme-glossy-inner-shine), var(--wa-theme-glossy-top-highlight), var(--wa-theme-glossy-bottom-shadow);
|
||||
}
|
||||
|
||||
|
||||
@@ -206,9 +206,8 @@
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
input[type='range']:hover,
|
||||
wa-slider:hover {
|
||||
--thumb-shadow: 0 0 0 0.5em color-mix(in oklab, var(--thumb-color), transparent 85%);
|
||||
wa-slider:hover::part(thumb) {
|
||||
box-shadow: 0 0 0 0.5em color-mix(in oklab, var(--wa-form-control-activated-color), transparent 85%);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -110,23 +110,22 @@
|
||||
);
|
||||
}
|
||||
|
||||
input[type='range'],
|
||||
wa-progress-bar,
|
||||
wa-slider {
|
||||
--shadow-lower: inset 0 -0.125em 0.5em
|
||||
oklab(from var(--indicator-color, var(--wa-form-control-activated-color)) calc(l - 0.2) a b);
|
||||
--shadow-upper: inset 0 0.125em 0.5em
|
||||
oklab(from var(--indicator-color, var(--wa-form-control-activated-color)) calc(l + 0.4) a b);
|
||||
|
||||
--thumb-shadow: var(--wa-shadow-s), var(--shadow-lower), var(--shadow-upper);
|
||||
|
||||
&::part(indicator) {
|
||||
box-shadow: var(--shadow-lower), var(--shadow-upper);
|
||||
}
|
||||
--shadow-lower: inset 0 -0.125em 0.5em oklab(from var(--wa-form-control-activated-color) calc(l - 0.2) a b);
|
||||
--shadow-upper: inset 0 0.125em 0.5em oklab(from var(--wa-form-control-activated-color) calc(l + 0.4) a b);
|
||||
}
|
||||
|
||||
wa-switch[checked] {
|
||||
--thumb-shadow: var(--wa-shadow-s);
|
||||
wa-slider::part(thumb) {
|
||||
box-shadow: var(--wa-shadow-s), var(--shadow-lower), var(--shadow-upper);
|
||||
}
|
||||
|
||||
wa-progress-bar::part(indicator) {
|
||||
box-shadow: var(--shadow-lower), var(--shadow-upper);
|
||||
}
|
||||
|
||||
wa-switch[checked]::part(thumb) {
|
||||
box-shadow: var(--wa-shadow-s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,9 +94,8 @@
|
||||
--checked-icon-scale: 0.4;
|
||||
}
|
||||
|
||||
input[type='range'],
|
||||
wa-slider {
|
||||
--thumb-gap: 0;
|
||||
wa-slider::part(thumb) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
wa-switch {
|
||||
|
||||
Reference in New Issue
Block a user