GrSlider

  • ядро
  • Формы
  • API · 17 props
  • ⌨ клавиши

Берут, когда точное число не важно.

import { GrSlider } from '@feugene/granularity/components/GrSlider'

Обзор

Когда брать

  • точное число не важно — громкость, яркость, прозрачность: важнее «больше — меньше»;
  • диапазон нагляден — min/max показывают границы, которых в поле ввода не видно;
  • нужен диапазон «от — до» — range даёт два бегунка, которые не проходят друг сквозь друга;
  • у шкалы есть опорные точки — marks подписывают ступени;
  • значение применяется по отпусканию — lazy не шлёт запрос на каждое движение.

Когда взять другое

НужноБерите
Нужно точное числоGrNumberInput
Оценка звёздамиGrRating
Ступеней немного и они названыGrSegmented / GrRadioGroup
Значение показывают, а не задаютGrProgressBar

Playground контролы выводятся из API, а не пишутся руками

Сцена

Загружается…

0 изменено· состояние — в адресе

Примеры 3 у каждого — живое демо и его код

  • Фильтр цены в каталоге

    range даёт два бегунка, которые не проходят друг сквозь друга, а клик по дорожке ведёт ближайший. lazy придерживает модель до отпускания — каталог спрашивает сервер раз за жест, а не на каждый пиксель, — и об этом сообщает change. marks расставляют ценовые ориентиры; слот mark красит попавшие в диапазон по active, который считается так же, как заливка.

    Price€100 – €500
    Drag a thumb, or focus it and use the arrow keys
    • Tromsø compact desk€129
    • Oslo writing desk€149
    • Kalmar corner desk€219
    • Uppsala desk with drawers€265
    • Bergen standing desk€389
    • Lund oak desk€459
  • Термостаты переговорных

    Термостат стоит вертикально: orientation="vertical" ставит минимум внизу, а высоту задаёт --gr-slider-length. Слот thumb пишет градусы внутри увеличенной ручки — он наполняет бегунок, а не заменяет его, поэтому роль, клавиши и aria-* остаются за компонентом, а marks отступают на размер этой ручки. Комната по расписанию — readonly: значение видно, но им владеет здание.

    21°
    LisbonUp to 8 people21 °C · comfortable
    19°
    PortoUp to 4 people19 °C · comfortable
  • Экспорт фотографии товара

    У PNG нет качества, поэтому слайдер disabled; ниже 40 ценник на фото рассыпается, и страница ставит invalid с причиной. show-tooltip="always" держит число на виду, format-tooltip отдаёт тот же текст в aria-valuetext, массив marks и step привязывают ширину к ходовым размерам, а name отправляет оба значения.

    Export «oak-desk-front.tif»About 171 KB

    Lower is smaller and blurrier

    80%

API порождается из пакета · разойтись не может

Props

PropTypeпо умолчаниюОписание
disabledboolean | undefinedfalseНедоступен: бегунки не двигаются и не фокусируются. Складывается по «или» с полем и формой.
readonlyboolean | undefinedfalseТолько для чтения: значение видно, но не меняется.
invalidboolean | undefinedfalseВизуальное и ARIA-состояние ошибки.
requiredboolean | undefinedfalseОбязательное поле (`aria-required`).
size"xs" | "sm" | "md" | "lg" | undefinedundefinedТолщина дорожки и размер бегунка. По умолчанию `md`; берётся из `GrConfigProvider`.
ariaLabelstring | undefinedundefinedДоступное имя вне `GrFormField`. В диапазоне бегунки получают его с пометкой «min»/«max».
namestring | undefinedundefinedИмя для нативной формы: hidden input на значение, при `range` — два с одним именем.
maxnumber | undefined100Верхняя граница шкалы. По умолчанию `100`.
orientation"horizontal" | "vertical" | undefined"horizontal"Ориентация дорожки. В вертикальной минимум внизу.
lazyboolean | undefinedfalseЭмитить `update:modelValue` только по завершении жеста: во время перетаскивания значение ведёт сам слайдер. Клавиатура коммитит сразу — нажатие клавиши дискретно, придерживать его нечего.
stepnumber | undefined1Шаг, к которому привязывается значение, — и шаг стрелок с клавиатуры. По умолчанию `1`.
minnumber | undefined0Нижняя граница шкалы; значение вне границ прижимается к ней. По умолчанию `0`.
rangeboolean | undefinedfalseДиапазон с двумя бегунками; модель — кортеж `[lo, hi]`.
marksGrSliderMarks | undefinedundefinedМетки делений: `{ [value]: label }` или массив значений.

Слоты

SlotTypeОписание
mark{ value: number; label: string; percent: number; active: boolean; }Подпись деления шкалы. Слот наполняет её узел, а не заменяет: позиция, `aria-hidden` и дублирование значения диктору остаются за компонентом.
thumb
{ index: number; value: number; percent: number; active: boo{ index: number; value: number; percent: number; active: boolean; disabled: boolean; }
Содержимое бегунка. Слот рендерится **внутрь** ручки, а не вместо неё: на самом узле висят `role="slider"`, `tabindex`, восемь `aria-*` и позиционирование, и отдать его разметке потребителя значит отдать вместе с ним весь виджетный контракт. Вид самой ручки меняется токенами `--gr-slider-thumb-*`.

События

EventСигнатураОписание
update:modelValue(event: "update:modelValue", value: GrSliderModelValue): void—
change(event: "change", value: GrSliderModelValue): void—
focus(event: "focus", event: FocusEvent): void—
blur(event: "blur", event: FocusEvent): void—

Методы

MethodsСигнатураОписание
focus() => voidПоставить фокус на бегунок; в диапазоне — на нижний.
blur() => voidСнять фокус с первого бегунка.

Типы

Все экспортируемые типы подпути, по файлам, где они объявлены. Имена, дженерики и тела читаются из файлов деклараций пакета и разойтись не могут.

Импорт
import type { GrSliderConfigurableProps, GrSliderSize, GrSliderModelValue } from '@feugene/granularity/components/GrSlider'

Файлdist/types/components/GrSlider/config.d.ts

grSliderConfigconst
const grSliderConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrSlider">;

Файлdist/types/components/GrSlider/defaults.d.ts

GrSliderConfigurablePropsinterface

Пропы GrSlider, настраиваемые глобально через <GrConfigProvider :component-defaults="{ GrSlider: { … } }">. Только оформление — см. GrButton/defaults.ts.

interface GrSliderConfigurableProps {
    size: GrSliderSize;
}

Файлdist/types/components/GrSlider/grSliderStyles.d.ts

GrSliderSizetype
type GrSliderSize = GrComponentSize;
GrSliderModelValuetype

Значение: число (single) или кортеж [lo, hi] (range при range=true).

type GrSliderModelValue = number | [number, number];
GrSliderMarkstype

Метка деления: словарь value → label или просто массив значений (label = значение).

type GrSliderMarks = Record<number, string> | number[];

Файлdist/types/components/GrSlider/GrSlider.vue.d.ts

GrSliderPropsinterface

Публичный GR-примитив «Slider» (WAI-ARIA slider pattern). Поддерживает одиночное значение и диапазон (range — два бегунка), шаг, метки делений, всплывающее значение (tooltip), полную клавиатурную навигацию (стрелки / PageUp/Down / Home / End) и role="slider" с aria-valuemin/max/now на каждом бегунке.

interface GrSliderProps {
    /** `number` — одиночное значение; `[lo, hi]` — диапазон (при `range=true`). */
    modelValue: GrSliderModelValue;
    /** Нижняя граница шкалы; значение вне границ прижимается к ней. По умолчанию `0`. */
    min?: number;
    /** Верхняя граница шкалы. По умолчанию `100`. */
    max?: number;
    /** Шаг, к которому привязывается значение, — и шаг стрелок с клавиатуры. По умолчанию `1`. */
    step?: number;
    /** Диапазон с двумя бегунками; модель — кортеж `[lo, hi]`. */
    range?: boolean;
    /** Недоступен: бегунки не двигаются и не фокусируются. Складывается по «или» с полем и формой. */
    disabled?: boolean;
    /** Только для чтения: значение видно, но не меняется. */
    readonly?: boolean;
    /** Визуальное и ARIA-состояние ошибки. */
    invalid?: boolean;
    /** Обязательное поле (`aria-required`). */
    required?: boolean;
    /** Толщина дорожки и размер бегунка. По умолчанию `md`; берётся из `GrConfigProvider`. */
    size?: GrSliderSize;
    /** Метки делений: `{ [value]: label }` или массив значений. */
    marks?: GrSliderMarks;
    /** Всплывающее значение над бегунком: `true`/`'hover'` — при hover/drag/focus, `'always'` — всегда. */
    showTooltip?: boolean | 'always' | 'hover';
    /** Форматирование значения в tooltip. Оно же уходит в `aria-valuetext`. */
    formatTooltip?: (value: number) => string;
GrSliderEmitsinterface
interface GrSliderEmits {
    /** Новое значение для `v-model` — на каждом шаге перетаскивания, при `lazy` только по отпусканию. */
    (e: 'update:modelValue', value: GrSliderModelValue): void;
    /** Значение зафиксировано (отпущен бегунок / клавиша). */
    (e: 'change', value: GrSliderModelValue): void;
    /** Фокус вошёл в слайдер. Переход между бегунками диапазона событием не считается. */
    (e: 'focus', event: FocusEvent): void;
    /** Фокус ушёл из слайдера целиком. */
    (e: 'blur', event: FocusEvent): void;
}

Файлdist/types/components/GrSlider/safelist.d.ts

grSliderSafelistconst
const grSliderSafelist: string[];

Файлdist/types/components/GrSlider/index.d.ts

GrSliderInstancetype
type GrSliderInstance = ComponentExposed<typeof GrSliderComponent>;

Доступность

Паттерн APG

slider

Клавиши

← / ↓→ / ↑PageUp / PageDownHome / End

КлавишаДействие
← / ↓шаг вниз
→ / ↑шаг вверх
PageUp / PageDownкрупный шаг
Home / Endк краям

Использование из прозы библиотеки

Слоты наполняют дорожку, а не заменяют её
vue
<GrSlider v-model="temperature" :marks="{ 16: 'холодно', 30: 'жарко' }">
  <template #thumb="{ value }">{{ value }}</template>
  <template #mark="{ label, active }">
    <span :class="active ? 'text-[var(--gr-primary-text)]' : ''">{{ label }}</span>
  </template>
</GrSlider>

#thumb рендерится внутрь ручки, а не вместо неё. На её узле висят role="slider", tabindex, восемь aria-* — включая aria-valuemin и aria-valuemax, которые у диапазона считаются от соседнего бегунка, — плюс обработчик клавиатуры и позиционирование. Отдать эту разметку потребителю значит отдать вместе с ней весь виджетный контракт.

Вид самой ручки при этом меняется токенами --gr-slider-thumb-bg, -border и -size, так что «совсем другая ручка» собирается связкой «токены + слот». Тултип со значением остаётся рядом со слотом, а не вытесняется им.

#mark получает active — попало ли деление в закрашенную часть. Ради него слот чаще всего и берут: снаружи это не вычислить, модель в скоуп иначе не приходит. У диапазона закрашен участок между бегунками, а не от нуля, поэтому деление слева от первого бегунка пройденным не считается — active считается тем же способом, что и сама заливка.

Подпись деления остаётся aria-hidden: диктор читает значение с бегунка, и второй текст внутри слайдера ему только мешает.

Текст тултипа задаётся не слотом, а formatTooltip: там нужна строка, а не разметка — подсказка живёт поверх ручки и своей раскладки не имеет.

Диапазон
vue
<GrSlider v-model="range" range :min="0" :max="100" aria-label="Бюджет" />

Модель — кортеж [lo, hi]; бегунки не перепрыгивают друг друга. Клик по дорожке уводит ближайший бегунок, а когда оба сошлись в точку — тот, в сторону которого кликнули: иначе схлопнутый диапазон было бы не развести мышью.

Каждый бегунок получает своё имя: ariaLabel плюс граница из локали (gr.slider.min / gr.slider.max) — «Бюджет (минимум)».

Ориентация
vue
<GrSlider v-model="volume" orientation="vertical" :style="{ '--gr-slider-length': '12rem' }" />

В вертикальной дорожке минимум внизу. Длина задаётся --gr-slider-length (по умолчанию 10rem), толщину по-прежнему держит --gr-slider-track-height. Подсказка уходит вбок — над бегунком она легла бы на дорожку, — а подписи меток встают справа. aria-orientation следует пропу, клавиатура не меняется: ↑/→ увеличивают, ↓/← уменьшают.

Когда значение уходит наружу

lazy придерживает update:modelValue до конца жеста: во время перетаскивания бегунок ведёт внутреннее значение, а наружу уходит одно событие на отпускании (вместе с change). Это для дорогих пересчётов, которым незачем срабатывать на каждое движение мыши.

Клавиатура коммитит сразу и при lazy: нажатие клавиши дискретно, придерживать его нечего.

Не доехавший modelValue ставит бегунок на min и объясняется предупреждением в dev-режиме: иначе смещение и aria-valuenow ушли бы в NaN, а диктор прочитал бы его вслух.

Метки и подсказка

marks — словарь { [value]: label } или массив значений. Подписи меток скрыты от скринридера (aria-hidden): значение он читает с самого бегунка, а подписи стали бы случайным текстом внутри слайдера.

formatTooltip управляет и подсказкой, и aria-valuetext — «$1 200» вместо голого «1200». Без своего формата aria-valuetext не выставляется: число самодостаточно.

Подписи меток отступают от бегунка, а не от дорожки: отступ — выступ ручки за дорожку плюс зазор, но не меньше штатного. Крупная ручка (--gr-slider-thumb-size вместе со слотом #thumb) поэтому не закрывает подписи даже стоя прямо на делении, а место под подписями растёт вместе с ней.

Оформление

size — xs…lg, читается из GrConfigProvider. Точечная кастомизация — через переменные: --gr-slider-rail, --gr-slider-fill, --gr-slider-thumb-bg, --gr-slider-thumb-border, --gr-slider-thumb-size, --gr-slider-track-height, --gr-slider-length.

disabled гасит заливку, бегунок (вместе с его рамкой), подсказку и подписи меток токенами --gr-disabled-*, а не прозрачностью: opacity разбавляет выверенные на AA цвета текста.

invalid — свой или из ошибки GrFormField — красит заливку и рамку бегунка ролью --gr-invalid-brd, а кольцо фокуса — --gr-invalid-ring, как рамку у полей ввода. Недоступность сильнее ошибки.

Нативная форма

Проп name рендерит input[type="hidden"] со значением; при range — два инпута с одним именем (lo, hi). Сериализуется модель (после снапа к step), а не черновик жеста при lazy.

Недоступный слайдер значение не отправляет — как нативный disabled контрол, GrSegmented и GrSwitch: скрытые инпуты (оба при range) получают disabled.