Porównaj commity

...

25 Commity

Autor SHA1 Wiadomość Data
Matt Pharoah f28e379fbb
Merge 3b83126851 into 64996b2d35 2024-04-13 18:48:29 +02:00
Konnor Rogers 64996b2d35
add changelog entry for button classes (#1976)
* add changelog entry

* prettier
2024-04-12 12:17:37 -04:00
Susanne Kirchner 0daa5d8dee
Fix invalid css on button style (#1975) 2024-04-12 12:07:48 -04:00
Konnor Rogers 16d5575307
Fix: split panel properly recalculates when going from hidden to shown (#1942)
* fix: split-panel now properly calculates it size when it goes from hidden to being shown.

* chore: add changelog note

* prettier
2024-04-11 14:09:56 -04:00
Konnor Rogers a427433701
fix: scrollbar gutters and dialog scrolling on open (#1967)
* fix: scrollbar gutters and dialog scrolling on open

* prettier

* fix check for current scrollbarGutter property

* prettier
2024-04-11 13:52:41 -04:00
Danny Andrews c6da4f5b14
Update docs for customizing button widths (#1973)
Currently, the docs state that you can set a width attribute to
customize the width of buttons, but no such attribute exists. I've
updated the docs to direct people to set a custom width via CSS through
inline styles or a custom class.
2024-04-11 12:32:54 -05:00
Matt Pharoah 3b83126851 Use localization for default tooltip formatter 2024-03-25 18:22:19 -04:00
Matt Pharoah 450e3296d0 Merge branch 'next' into mpharoah/multi-range 2024-03-25 18:16:58 -04:00
Matt Pharoah 2442485656 Added RTL support 2024-03-25 18:14:41 -04:00
Matt Pharoah c783d25ae3 Fixed validity().valid having the incorrect value 2024-03-11 16:36:57 -04:00
Matt Pharoah 33ba9cbd7b Fixed typo 2024-03-11 16:33:50 -04:00
Matt Pharoah 34570d505d Only trigger re-render on setting valueAsArray if the arrays are actually different 2024-03-11 16:31:07 -04:00
Matt Pharoah 867428d301 Fixed updating via valueAsArray property 2024-03-11 16:27:50 -04:00
Matt Pharoah b2107bb2eb Added CSS parts 2024-03-11 16:17:12 -04:00
Matt Pharoah 5a23dff800 Added form integration 2024-03-11 16:11:28 -04:00
Matt Pharoah 32f8922ccc Ensure the tooltip stays in the correct position if the slider is resized 2024-03-11 15:26:45 -04:00
Matt Pharoah 9d01704d14 Make value a string for form compatability and expose array via the valueAsArray property instead 2024-03-11 15:04:21 -04:00
Matt Pharoah 5530db5faf Parse input as comma separated values instead of a JSON array 2024-03-11 14:51:01 -04:00
Matt Pharoah 5ba73cadb3 Support tooltips 2024-03-11 13:22:32 -04:00
Matt Pharoah 454256db74 Prettier fix 2024-03-11 12:25:08 -04:00
Matt Pharoah f6cfdb2f09 Force the focus-visible style for slider handles if the keyboard is used to move them, even if they were originally focused via the mouse cursor 2024-03-11 12:13:28 -04:00
Matt Pharoah 6686f3397b Added sl-focus and sl-blur events 2024-03-11 11:55:57 -04:00
Matt Pharoah 6cfa770b24 Changed default value for --track-color-active 2024-03-11 11:38:06 -04:00
Matt Pharoah 56a24b37b9 Fully support label and help-text 2024-03-11 11:31:13 -04:00
Matt Pharoah ca71a8939c Initial sl-multi-range prototype 2024-03-07 18:28:46 -05:00
11 zmienionych plików z 783 dodań i 3 usunięć

Wyświetl plik

@ -236,7 +236,7 @@ When a `target` is set, the link will receive `rel="noreferrer noopener"` for [s
### Setting a Custom Width
As expected, buttons can be given a custom width by setting the `width` attribute. This is useful for making buttons span the full width of their container on smaller screens.
As expected, buttons can be given a custom width by passing inline styles to the component (or using a class). This is useful for making buttons span the full width of their container on smaller screens.
```html:preview
<sl-button variant="default" size="small" style="width: 100%; margin-bottom: 1rem;">Small</sl-button>

Wyświetl plik

@ -0,0 +1,109 @@
---
meta:
title: Multi-Range
description: Multi-Ranges allow the user to select multiple values within a given range using a slider with multiple handles.
layout: component
---
```html:preview
<sl-multi-range></sl-multi-range>
```
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/multi-range';
const App = () => <SlRange />;
```
## Examples
### Min, Max, and Step
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.
```html:preview
<sl-multi-range min="1" max="10" step="1" value="0,10"></sl-multi-range>
```
{% raw %}
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/multi-range';
const App = () => <SlRange min={1} max={10} step={1} value={'0,10'}/>;
```
{% endraw %}
### Disabled
Use the `disabled` attribute to disable a slider.
```html:preview
<sl-multi-range disabled></sl-multi-range>
```
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/multi-range';
const App = () => <SlRange disabled />;
```
### Arbitrary Number of Handles
You can use any number of handles on the slider. The slider will have one handle for every element in the value array.
```html:preview
<sl-multi-range value="25,50,75"></sl-multi-range>
```
{% raw %}
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/multi-range';
const App = () => <SlRange value={'25,50,75'} />;
```
{% endraw %}
### Label and Help Text
You can add an accessible label and/or descriptive help text using the `label` and `help-text` attributes or slots.
```html:preview
<sl-multi-range label="Difficulty Range" help-text="Search for challenges within the desired difficulty range"></sl-multi-range>
```
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/multi-range';
const App = () => <SlRange label="Difficulty Range" help-text="Search for challenges within the desired difficulty range" />;
```
### 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:preview
<sl-multi-range
value="25,75"
style="
--track-color-active: var(--sl-color-green-300);
--track-color-inactive: var(--sl-color-red-300);
"
></sl-multi-range>
```
{% raw %}
```jsx:react
import SlRange from '@shoelace-style/shoelace/dist/react/multi-range';
const App = () => <SlRange value={'25,75'} style={{
'--track-color-active': 'var(--sl-color-green-300)',
'--track-color-inactive': 'var(--sl-color-red-300)'
}}/>;
```
{% endraw %}

Wyświetl plik

@ -14,7 +14,10 @@ New versions of Shoelace are released as-needed and generally occur when a criti
## Next
- Fixed a bug in `<sl-split-panel>` that caused it not to recalculate it's position when going from being `display: none;` to its original display value. [#1942]
- Fixed a bug in `<dialog>` where when it showed it would cause a layout shift. [#1967]
- Fixed a bug in `<sl-tooltip>` that allowed unwanted text properties to leak in [#1947]
- Fixed a bug in `<sl-button-group>` classes [#1974]
## 2.15.0

Wyświetl plik

@ -590,7 +590,7 @@ export default css`
/* Focus and checked are always on top */
:host([data-sl-button-group__button--focus]),
:host([data-sl-button-group__button[checked]]) {
:host([data-sl-button-group__button][checked]) {
z-index: 2;
}
`;

Wyświetl plik

@ -0,0 +1,499 @@
import { classMap } from 'lit/directives/class-map.js';
import { defaultValue } from '../../internal/default-value.js';
import { FormControlController } from '../../internal/form.js';
import { HasSlotController } from '../../internal/slot.js';
import { html, nothing } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { LocalizeController } from '../../utilities/localize.js';
import { property, query, queryAll } from 'lit/decorators.js';
import componentStyles from '../../styles/component.styles.js';
import formControlStyles from '../../styles/form-control.styles.js';
import ShoelaceElement from '../../internal/shoelace-element.js';
import styles from './multi-range.styles.js';
import type { CSSResultGroup, PropertyValues } from 'lit';
import type { ShoelaceFormControl } from '../../internal/shoelace-element.js';
const numericSort = function (a: number, b: number): number {
return a - b;
};
const arraysDiffer = function (a: readonly number[], b: readonly number[]): boolean {
a ||= [];
b ||= [];
if (a.length !== b.length) return true;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return true;
}
return false;
};
/**
* @summary Multi-Ranges allow the user to select multiple values within a given range using a slider with multiple handles.
* @documentation https://shoelace.style/components/multi-range
* @status experimental
* @since next
*
* @slot label - The range's label. Alternatively, you can use the `label` attribute.
* @slot help-text - Text that describes how to use the input. Alternatively, you can use the `help-text` attribute.
*
* @event sl-blur - Emitted when the control loses focus.
* @event sl-change - Emitted when an alteration to the control's value is committed by the user.
* @event sl-focus - Emitted when the control gains focus.
* @event sl-input - Emitted when the control receives input.
*
* @csspart form-control - The form control that wraps the label, input, and help text.
* @csspart form-control-label - The label's wrapper.
* @csspart form-control-help-text - The help text's wrapper.
* @csspart base - The component's base wrapper.
* @csspart tooltip - The range's tooltip.
*
* @cssproperty --thumb-size - The size of the thumb.
* @cssproperty --tooltip-offset - The vertical distance the tooltip is offset from the track.
* @cssproperty --track-color-active - The color of the portion of the track that represents the current value.
* @cssproperty --track-color-inactive - The of the portion of the track that represents the remaining value.
* @cssproperty --track-height - The height of the track.
*/
export default class SlMultiRange extends ShoelaceElement implements ShoelaceFormControl {
static styles: CSSResultGroup = [componentStyles, formControlStyles, styles];
/** The name of the range, submitted as a name/value pair with form data. */
@property() name = '';
/** The range's label. If you need to display HTML, use the `label` slot instead. */
@property() label = '';
/** The range's help text. If you need to display HTML, use the help-text slot instead. */
@property({ attribute: 'help-text' }) helpText = '';
/** Disables the range. */
@property({ type: Boolean, reflect: true }) disabled = false;
/** The minimum acceptable value of the range. */
@property({ type: Number }) min = 0;
/** The maximum acceptable value of the range. */
@property({ type: Number }) max = 100;
/** The interval at which the range will increase and decrease. */
@property({ type: Number }) step = 1;
/** The preferred placement of the range's tooltip. */
@property() tooltip: 'top' | 'bottom' | 'none' = 'top';
/** The current values of the input (in ascending order) as a string of comma-separated values */
@property({ type: String })
set value(value: string | null) {
this.#value = value ? value.split(',').map(n => +n) : [];
}
get value() {
return this.#value.join(',');
}
/** Gets or sets the current values of the range as an array of numbers */
set valueAsArray(value: readonly number[] | null) {
const oldValue = this.#value;
this.#value = value || [];
if (arraysDiffer(oldValue, this.#value)) {
this.requestUpdate('value', oldValue.join(','));
}
}
get valueAsArray() {
return this.#value;
}
/** The default value of the form control. Primarily used for resetting the form control. */
@defaultValue() defaultValue = '0,100';
/**
* A function used to format the tooltip's value. The range's value is passed as the first and only argument. The
* function should return a string to display in the tooltip.
*/
@property({ attribute: false }) tooltipFormatter: (value: number) => string;
@query('.base') baseDiv: HTMLDivElement;
@query('.active-track') activeTrack: HTMLDivElement;
@query('.tooltip') tooltipElem: HTMLDivElement | undefined;
@queryAll('.handle') handles: NodeListOf<HTMLDivElement>;
#hasSlotController = new HasSlotController(this, 'help-text', 'label');
#formControlController = new FormControlController(this, { assumeInteractionOn: ['sl-change'] });
#localize = new LocalizeController(this);
#resizeObserver: ResizeObserver | null = null;
#value: readonly number[] = [0, 100];
#sliderValues = new Map<number, number>();
#hasFocus = false;
#validationError = '';
#nextId = 1;
get #rtl() {
return this.#localize.dir() === 'rtl';
}
constructor() {
super();
this.tooltipFormatter = this.#localize.number.bind(this.#localize);
}
override render(): unknown {
const hasLabel = !!(this.label || this.#hasSlotController.test('label'));
const hasHelpText = !!(this.helpText || this.#hasSlotController.test('help-text'));
const tooltip =
this.tooltip !== 'none' ? html`<div class="tooltip" part="tooltip" aria-hidden="true"></div>` : nothing;
this.#sliderValues.clear();
const handles = this.#value.map(value => {
const sliderId = this.#nextId++;
this.#sliderValues.set(sliderId, value);
return html`
<div
class="handle"
tabindex="${this.disabled ? -1 : 0}"
role="slider"
aria-labelledby=${ifDefined(hasLabel ? 'label' : undefined)}
aria-valuemin="${this.min}"
aria-valuemax="${this.max}"
aria-disabled=${ifDefined(this.disabled ? 'true' : undefined)}
aria-valuenow="${value}"
data-slider-id="${sliderId}"
@pointerdown=${this.#onClickHandle}
@pointermove=${this.#onDragHandle}
@pointerup=${this.#onReleaseHandle}
@pointercancel=${this.#onReleaseHandle}
@keydown=${this.#onKeyPress}
@focus=${this.#onFocusHandle}
></div>
`;
});
return html`
<div
part="form-control"
class=${classMap({
'form-control': true,
'form-control--medium': true, // range only has one size
'form-control--has-label': hasLabel,
'form-control--has-help-text': hasHelpText,
'tooltip-top': this.tooltip === 'top',
'tooltip-bottom': this.tooltip === 'bottom'
})}
@focusout=${this.#onBlur}
>
<label
id="label"
part="form-control-label"
class="form-control__label"
aria-hidden=${hasLabel ? 'false' : 'true'}
>
<slot name="label">${this.label}</slot>
</label>
<div class="base" part="base">
<div class="track"></div>
<div class="active-track"></div>
${handles} ${tooltip}
</div>
<div
part="form-control-help-text"
class="form-control__help-text"
aria-hidden=${hasHelpText ? 'false' : 'true'}
>
<slot name="help-text">${this.helpText}</slot>
</div>
</div>
`;
}
protected override willUpdate(changedProperties: PropertyValues): void {
super.willUpdate(changedProperties);
if (this.tooltip !== 'none' && !this.#resizeObserver) {
this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));
this.updateComplete.then(() => {
this.#resizeObserver?.observe(this.baseDiv);
});
} else if (this.tooltip === 'none' && this.#resizeObserver) {
this.#resizeObserver.disconnect();
this.#resizeObserver = null;
}
if (this.min > this.max) {
[this.min, this.max] = [this.max, this.min];
}
if (this.step > this.max - this.min) {
this.step = this.max - this.min;
}
if (this.step <= 0) {
this.step = 1;
}
const adjustedValue = this.#value
.map(value => {
if (value <= this.min) return this.min;
if (value >= this.max) return this.max;
value = this.min + this.step * Math.round((value - this.min) / this.step);
if (value > this.max) return this.max;
return value;
})
.sort(numericSort);
if (arraysDiffer(this.#value, adjustedValue)) {
this.#value = adjustedValue;
if (!changedProperties.has('value')) {
this.emit('sl-change');
}
}
}
protected override updated(changedProperties: PropertyValues): void {
super.updated(changedProperties);
for (const handle of this.handles) {
const sliderId = +handle.dataset.sliderId!;
if (!this.#sliderValues.has(sliderId)) continue;
this.#moveHandle(handle, this.#sliderValues.get(sliderId)!);
}
this.#updateActiveTrack();
}
override focus(options?: FocusOptions): void {
const firstHandle = this.handles.item(0);
if (firstHandle) {
firstHandle.focus(options);
} else {
super.focus(options);
}
}
/** Checks for validity but does not show a validation message. Returns `true` when valid and `false` when invalid. */
public checkValidity(): boolean {
return !this.#validationError;
}
/** Checks for validity and shows the browser's validation message if the control is invalid. */
public reportValidity(): boolean {
this.#validationError = '';
return true;
}
/** Sets a custom validation message. Pass an empty string to restore validity. */
public setCustomValidity(message: string): void {
this.#validationError = message;
this.#formControlController.updateValidity();
}
/** Gets the associated form, if one exists. */
public getForm(): HTMLFormElement | null {
return this.#formControlController.getForm();
}
/** Gets the validity state object */
public get validity(): ValidityState {
return {
badInput: false,
customError: !!this.#validationError,
patternMismatch: false,
rangeOverflow: false,
rangeUnderflow: false,
stepMismatch: false,
tooLong: false,
tooShort: false,
typeMismatch: false,
valid: !this.#validationError,
valueMissing: false
};
}
/** Gets the validation message */
public get validationMessage(): string {
return this.#validationError;
}
#onClickHandle(event: PointerEvent): void {
if (this.disabled) return;
this.baseDiv?.classList?.add('tooltip-visible');
const handle = event.target as HTMLDivElement;
this.#updateTooltip(handle);
if (handle.dataset.pointerId) {
handle.releasePointerCapture(+handle.dataset.pointerId);
}
if (this.disabled) return;
handle.dataset.pointerId = event.pointerId.toString();
handle.setPointerCapture(event.pointerId);
handle.classList.add('grabbed');
}
#onDragHandle(event: PointerEvent): void {
if (this.disabled) return;
const handle = event.target as HTMLDivElement;
const sliderId = +handle.dataset.sliderId!;
if (!this.#sliderValues.has(sliderId)) return;
const pointerId = handle.dataset.pointerId ? +handle.dataset.pointerId : null;
if (pointerId !== event.pointerId) return;
const pos = this.#getNormalizedValueFromClientX(handle, event.clientX);
const unit = this.step / (this.max - this.min);
const value = this.min + this.step * Math.round(pos / unit);
this.#sliderValues.set(sliderId, value);
this.#moveHandle(handle, value);
const prevValue = this.#value;
this.#value = Array.from(this.#sliderValues.values()).sort(numericSort);
this.#updateActiveTrack();
if (arraysDiffer(prevValue, this.#value)) {
this.emit('sl-input');
}
}
#getNormalizedValueFromClientX(handle: HTMLDivElement, x: number): number {
const bounds = this.baseDiv.getBoundingClientRect();
const size = bounds.width - handle.clientWidth;
if (size <= 0) return 0;
x -= bounds.left + handle.clientWidth / 2;
if (x <= 0) return this.#rtl ? 1 : 0;
if (x >= size) return this.#rtl ? 0 : 1;
x /= size;
return this.#rtl ? 1.0 - x : x;
}
#updateActiveTrack(): void {
const activeTrack = this.activeTrack;
if (!activeTrack) return;
if (this.min === this.max || this.#value.length < 2) {
activeTrack.style.display = 'none';
activeTrack.style.insetInlineStart = '0';
activeTrack.style.width = '0';
return;
}
const start = (100 * (this.#value[0] - this.min)) / (this.max - this.min);
const span = (100 * (this.#value[this.#value.length - 1] - this.#value[0])) / (this.max - this.min);
activeTrack.style.display = 'inline-block';
activeTrack.style.insetInlineStart = `${start}%`;
activeTrack.style.width = `${span}%`;
}
#onKeyPress(event: KeyboardEvent): void {
const handle = event.target as HTMLDivElement;
const sliderId = +handle.dataset.sliderId!;
let value = this.#sliderValues.get(sliderId);
if (value === undefined) return;
switch (event.key) {
case 'ArrowUp':
case 'Up':
value = Math.min(value + this.step, this.max);
break;
case 'ArrowDown':
case 'Down':
value = Math.max(value - this.step, this.min);
break;
case 'ArrowLeft':
case 'Left':
value = this.#rtl ? Math.min(value + this.step, this.max) : Math.max(value - this.step, this.min);
break;
case 'ArrowRight':
case 'Right':
value = this.#rtl ? Math.max(value - this.step, this.min) : Math.min(value + this.step, this.max);
break;
case 'PageUp':
value = Math.min(value + 10 * this.step, this.max);
break;
case 'PageDown':
value = Math.max(value - 10 * this.step, this.min);
break;
case 'Home':
value = this.min;
break;
case 'End':
value = this.max;
break;
default:
return;
}
this.baseDiv.classList.add('keyboard-focus');
if (value !== this.#sliderValues.get(sliderId)) {
this.#moveHandle(handle, value);
this.#sliderValues.set(sliderId, value);
this.#value = Array.from(this.#sliderValues.values()).sort(numericSort);
this.#updateActiveTrack();
this.emit('sl-input');
this.emit('sl-change');
}
event.stopPropagation();
event.preventDefault();
}
#onReleaseHandle(event: PointerEvent) {
this.baseDiv?.classList?.remove('tooltip-visible');
const handle = event.target as HTMLDivElement;
if (!handle.dataset.pointerId || event.pointerId !== +handle.dataset.pointerId) return;
handle.classList.remove('grabbed');
handle.releasePointerCapture(event.pointerId);
delete handle.dataset.pointerId;
this.emit('sl-change');
}
#moveHandle(handle: HTMLDivElement, value: number): void {
handle.setAttribute('aria-valuenow', value.toString());
handle.setAttribute('aria-valuetext', this.tooltipFormatter(value));
const pos = (value - this.min) / (this.max - this.min);
handle.style.insetInlineStart = `calc( ${100 * pos}% - var(--thumb-size) * ${pos} )`;
this.#updateTooltip(handle);
}
#onBlur(event: FocusEvent): void {
this.baseDiv?.classList?.remove('tooltip-visible');
this.baseDiv?.classList?.remove('keyboard-focus');
if (event.relatedTarget && this.shadowRoot?.contains(event.relatedTarget as Node)) return;
this.emit('sl-blur');
this.#hasFocus = false;
}
#updateTooltip(handle: HTMLDivElement): void {
const sliderId = +handle.dataset.sliderId!;
if (!this.tooltipElem) return;
if (!this.baseDiv?.classList?.contains('tooltip-visible')) return;
if (!this.#sliderValues.has(sliderId)) return;
const value = this.#sliderValues.get(sliderId)!;
let pos = (value - this.min) / (this.max - this.min);
if (this.#rtl) pos = 1.0 - pos;
this.tooltipElem.style.translate = `calc( ${pos} * ( ${this.baseDiv.offsetWidth}px - var(--thumb-size) ) - 50% + (var(--thumb-size) / 2) )`;
this.tooltipElem.innerText = this.tooltipFormatter(value);
}
#onFocusHandle(event: FocusEvent): void {
if (this.disabled) return;
if (!this.#hasFocus) {
this.#hasFocus = true;
this.emit('sl-focus');
}
const handle = event.target as HTMLDivElement;
if (!handle?.dataset?.sliderId) return;
this.baseDiv?.classList?.add('tooltip-visible');
this.#updateTooltip(handle);
}
#onResize(): void {
const handle = this.shadowRoot?.querySelector('.tooltip-visible .handle:focus');
if (handle) this.#updateTooltip(handle as HTMLDivElement);
}
}

Wyświetl plik

@ -0,0 +1,132 @@
import { css } from 'lit';
export default css`
:host {
--thumb-size: 20px;
--tooltip-offset: 10px;
--track-color-active: var(--sl-color-primary-300);
--track-color-inactive: var(--sl-color-neutral-200);
--track-height: 6px;
}
.form-control {
display: flex;
flex-direction: column;
align-items: stretch;
box-sizing: border-box;
writing-mode: horizontal-tb;
}
.base {
display: block;
position: relative;
height: var(--thumb-size);
}
:host([disabled]) .base {
opacity: 0.5;
}
.track {
display: inline-block;
height: var(--track-height);
width: calc(100% + 6px - var(--thumb-size));
border-radius: 3px;
margin: calc((var(--thumb-size) - var(--track-height)) / 2) calc(var(--thumb-size) / 2 - 3px);
background-color: var(--track-color-inactive);
}
.active-track {
position: absolute;
top: calc((var(--thumb-size) - var(--track-height)) / 2);
height: var(--track-height);
background-color: var(--track-color-active);
z-index: 2;
}
.handle {
display: block;
position: absolute;
top: 0;
width: var(--thumb-size);
height: var(--thumb-size);
border-radius: 50%;
background-color: var(--sl-color-primary-600);
z-index: 3;
cursor: pointer;
}
.handle:hover,
.handle.grabbed {
background-color: var(--sl-color-primary-500);
}
.handle.grabbed {
cursor: grabbing;
}
.handle:focus-visible,
.keyboard-focus .handle:focus {
outline: var(--sl-focus-ring);
outline-offset: var(--sl-focus-ring-offset);
}
:host([disabled]) .handle,
:host([disabled]) .handle.grabbed {
cursor: not-allowed;
}
.tooltip {
position: absolute;
z-index: var(--sl-z-index-tooltip);
left: 0;
border-radius: var(--sl-tooltip-border-radius);
background-color: var(--sl-tooltip-background-color);
font-family: var(--sl-tooltip-font-family);
font-size: var(--sl-tooltip-font-size);
font-weight: var(--sl-tooltip-font-weight);
line-height: var(--sl-tooltip-line-height);
color: var(--sl-tooltip-color);
opacity: 0;
padding: var(--sl-tooltip-padding);
transition: var(--sl-transition-fast) opacity;
pointer-events: none;
}
.tooltip:after {
content: '';
position: absolute;
width: 0;
height: 0;
left: 50%;
translate: calc(-1 * var(--sl-tooltip-arrow-size));
}
.tooltip-visible .tooltip {
opacity: 1;
}
/* Tooltip on top */
.tooltip-top .tooltip {
top: calc(-1 * var(--thumb-size) - var(--tooltip-offset));
}
.tooltip-top .tooltip:after {
border-top: var(--sl-tooltip-arrow-size) solid var(--sl-tooltip-background-color);
border-left: var(--sl-tooltip-arrow-size) solid transparent;
border-right: var(--sl-tooltip-arrow-size) solid transparent;
top: 100%;
}
/* Tooltip on bottom */
.tooltip-bottom .tooltip {
bottom: calc(-1 * var(--thumb-size) - var(--tooltip-offset));
}
.tooltip-bottom .tooltip:after {
border-bottom: var(--sl-tooltip-arrow-size) solid var(--sl-tooltip-background-color);
border-left: var(--sl-tooltip-arrow-size) solid transparent;
border-right: var(--sl-tooltip-arrow-size) solid transparent;
bottom: 100%;
}
`;

Wyświetl plik

@ -0,0 +1,12 @@
import SlMultiRange from './multi-range.component.js';
export * from './multi-range.component.js';
export default SlMultiRange;
SlMultiRange.define('sl-multi-range');
declare global {
interface HTMLElementTagNameMap {
'sl-multi-range': SlMultiRange;
}
}

Wyświetl plik

@ -189,6 +189,14 @@ export default class SlSplitPanel extends ShoelaceElement {
const { width, height } = entries[0].contentRect;
this.size = this.vertical ? height : width;
// There's some weird logic that gets `this.cachedPositionInPixels = NaN` or `this.position === Infinity` when
// a split-panel goes from `display: none;` to showing.
if (isNaN(this.cachedPositionInPixels) || this.position === Infinity) {
this.cachedPositionInPixels = Number(this.getAttribute('position-in-pixels'));
this.positionInPixels = Number(this.getAttribute('position-in-pixels'));
this.position = this.pixelsToPercentage(this.positionInPixels);
}
// Resize when a primary panel is set
if (this.primary) {
this.position = this.pixelsToPercentage(this.cachedPositionInPixels);

Wyświetl plik

@ -33,6 +33,19 @@ export function lockBodyScrolling(lockingEl: HTMLElement) {
if (!document.documentElement.classList.contains('sl-scroll-lock')) {
/** Scrollbar width + body padding calculation can go away once Safari has scrollbar-gutter support. */
const scrollbarWidth = getScrollbarWidth() + getExistingBodyPadding(); // must be measured before the `sl-scroll-lock` class is applied
let scrollbarGutterProperty = getComputedStyle(document.documentElement).scrollbarGutter;
// default is auto, unsupported browsers is "undefined"
if (!scrollbarGutterProperty || scrollbarGutterProperty === 'auto') {
scrollbarGutterProperty = 'stable';
}
if (scrollbarWidth <= 0) {
// if there's no scrollbar, just set it to "revert" so whatever the user has set gets used. This is useful is the page is not overflowing and showing a scrollbar, or if the user has overflow: hidden, or any other reason a scrollbar may not be showing.
scrollbarGutterProperty = 'revert';
}
document.documentElement.style.setProperty('--sl-scroll-lock-gutter', scrollbarGutterProperty);
document.documentElement.classList.add('sl-scroll-lock');
document.documentElement.style.setProperty('--sl-scroll-lock-size', `${scrollbarWidth}px`);
}

Wyświetl plik

@ -30,6 +30,7 @@ export { default as SlInput } from './components/input/input.js';
export { default as SlMenu } from './components/menu/menu.js';
export { default as SlMenuItem } from './components/menu-item/menu-item.js';
export { default as SlMenuLabel } from './components/menu-label/menu-label.js';
export { default as SlMultiRange } from './components/multi-range/multi-range.js';
export { default as SlMutationObserver } from './components/mutation-observer/mutation-observer.js';
export { default as SlOption } from './components/option/option.js';
export { default as SlPopup } from './components/popup/popup.js';

Wyświetl plik

@ -6,7 +6,10 @@
@supports (scrollbar-gutter: stable) {
.sl-scroll-lock {
scrollbar-gutter: stable !important;
scrollbar-gutter: var(--sl-scroll-lock-gutter) !important;
}
.sl-scroll-lock body {
overflow: hidden !important;
}
}