GrSlider
Берут, когда точное число не важно.
Обзор
Когда брать
- точное число не важно — громкость, яркость, прозрачность: важнее «больше — меньше»;
- диапазон нагляден —
min/maxпоказывают границы, которых в поле ввода не видно; - нужен диапазон «от — до» —
rangeдаёт два бегунка, которые не проходят друг сквозь друга; - у шкалы есть опорные точки —
marksподписывают ступени; - значение применяется по отпусканию —
lazyне шлёт запрос на каждое движение.
Когда взять другое
| Нужно | Берите |
|---|---|
| Нужно точное число | GrNumberInput |
| Оценка звёздами | GrRating |
| Ступеней немного и они названы | GrSegmented / GrRadioGroup |
| Значение показывают, а не задают | GrProgressBar |
Playground контролы выводятся из API, а не пишутся руками
Загружается…
0 изменено· состояние — в адресе
<GrSlider
v-model="value"
showTooltip
ariaLabel="Notification volume"
/>Примеры 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: значение видно, но им владеет здание.LisbonUp to 8 people21 °C · comfortablePortoUp to 4 people19 °C · comfortableЭкспорт фотографии товара
У PNG нет качества, поэтому слайдер
disabled; ниже 40 ценник на фото рассыпается, и страница ставитinvalidс причиной.show-tooltip="always"держит число на виду,format-tooltipотдаёт тот же текст вaria-valuetext, массивmarksиstepпривязывают ширину к ходовым размерам, аnameотправляет оба значения.
API порождается из пакета · разойтись не может
Props
| Prop | Type | по умолчанию | Описание |
|---|---|---|---|
disabled | boolean | undefined | false | Недоступен: бегунки не двигаются и не фокусируются. Складывается по «или» с полем и формой. |
readonly | boolean | undefined | false | Только для чтения: значение видно, но не меняется. |
invalid | boolean | undefined | false | Визуальное и ARIA-состояние ошибки. |
required | boolean | undefined | false | Обязательное поле (`aria-required`). |
size | "xs" | "sm" | "md" | "lg" | undefined | undefined | Толщина дорожки и размер бегунка. По умолчанию `md`; берётся из `GrConfigProvider`. |
ariaLabel | string | undefined | undefined | Доступное имя вне `GrFormField`. В диапазоне бегунки получают его с пометкой «min»/«max». |
name | string | undefined | undefined | Имя для нативной формы: hidden input на значение, при `range` — два с одним именем. |
max | number | undefined | 100 | Верхняя граница шкалы. По умолчанию `100`. |
orientation | "horizontal" | "vertical" | undefined | "horizontal" | Ориентация дорожки. В вертикальной минимум внизу. |
lazy | boolean | undefined | false | Эмитить `update:modelValue` только по завершении жеста: во время перетаскивания значение ведёт сам слайдер. Клавиатура коммитит сразу — нажатие клавиши дискретно, придерживать его нечего. |
step | number | undefined | 1 | Шаг, к которому привязывается значение, — и шаг стрелок с клавиатуры. По умолчанию `1`. |
min | number | undefined | 0 | Нижняя граница шкалы; значение вне границ прижимается к ней. По умолчанию `0`. |
range | boolean | undefined | false | Диапазон с двумя бегунками; модель — кортеж `[lo, hi]`. |
marks | GrSliderMarks | undefined | undefined | Метки делений: `{ [value]: label }` или массив значений. |
showTooltip | boolean | "hover" | "always" | undefined | false | Всплывающее значение над бегунком: `true`/`'hover'` — при hover/drag/focus, `'always'` — всегда. |
formatTooltip | ((value: number) => string) | undefined | undefined | Форматирование значения в tooltip. Оно же уходит в `aria-valuetext`. |
modelValueобязательныйv-model | GrSliderModelValue | — | `number` — одиночное значение; `[lo, hi]` — диапазон (при `range=true`). |
Слоты
| Slot | Type | Описание |
|---|---|---|
mark | { value: number; label: string; percent: number; active: boolean; } | Подпись деления шкалы. Слот наполняет её узел, а не заменяет: позиция, `aria-hidden` и дублирование значения диктору остаются за компонентом. |
thumb |
| Содержимое бегунка. Слот рендерится **внутрь** ручки, а не вместо неё: на самом узле висят `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
const grSliderConfig: import("@feugene/granum/contract").GranumComponentDescriptor<"GrSlider">;Файлdist/types/components/GrSlider/defaults.d.ts
Пропы GrSlider, настраиваемые глобально через
<GrConfigProvider :component-defaults="{ GrSlider: { … } }">.
Только оформление — см. GrButton/defaults.ts.
interface GrSliderConfigurableProps {
size: GrSliderSize;
}Файлdist/types/components/GrSlider/grSliderStyles.d.ts
type GrSliderSize = GrComponentSize;Значение: число (single) или кортеж [lo, hi] (range при range=true).
type GrSliderModelValue = number | [number, number];Метка деления: словарь value → label или просто массив значений (label = значение).
type GrSliderMarks = Record<number, string> | number[];Файлdist/types/components/GrSlider/GrSlider.vue.d.ts
Публичный 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;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
const grSliderSafelist: string[];Файлdist/types/components/GrSlider/index.d.ts
type GrSliderInstance = ComponentExposed<typeof GrSliderComponent>;Доступность
Паттерн APG
slider
Клавиши
← / ↓→ / ↑PageUp / PageDownHome / End
| Клавиша | Действие |
|---|---|
| ← / ↓ | шаг вниз |
| → / ↑ | шаг вверх |
| PageUp / PageDown | крупный шаг |
| Home / End | к краям |
Полный контракт пакета: docs/keyboard.md ↗
Использование из прозы библиотеки
Слоты наполняют дорожку, а не заменяют её
<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: там нужна строка, а не
разметка — подсказка живёт поверх ручки и своей раскладки не имеет.
Диапазон
<GrSlider v-model="range" range :min="0" :max="100" aria-label="Бюджет" />Модель — кортеж [lo, hi]; бегунки не перепрыгивают друг друга. Клик по дорожке
уводит ближайший бегунок, а когда оба сошлись в точку — тот, в сторону
которого кликнули: иначе схлопнутый диапазон было бы не развести мышью.
Каждый бегунок получает своё имя: ariaLabel плюс граница из локали
(gr.slider.min / gr.slider.max) — «Бюджет (минимум)».
Ориентация
<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.
Пропы этого примера