rate — Rate
A rate turns “scoring” into discrete star selection: the user lights up stars instead of typing a number. core/rate handles the per-star fill tier (full/half/empty), half-star stepping and floating-point accumulation, and APG slider semantics — at the accessibility level, a rate control is a slider, not a radio group.
When to use
Section titled “When to use”- Each port implements
LhRate: product/service reviews, difficulty marking, satisfaction surveys. - Scenarios where the value domain is integers from 1 to N (or 0.5 stepping when half stars are allowed).
- Should not use it: when precise decimals or continuous values in an arbitrary range are needed (use
sliderorinput-number); when it is read-only display with no interaction needed, rate can still be used but should be switched to thereadonlystate.
Import
Section titled “Import”import { LH_RATE_MAX_DEFAULT, clampRate, stepRate, getStarFill, getRateAriaText, getRateClassNames, getRateAria,} from '@lh-ui/share/core/rate'import type { LhRateState } from '@lh-ui/share/core/rate'Design intent
Section titled “Design intent”stepRate does Math.round(next * 10) / 10 after adding/subtracting — this is not redundant, it is a defense against floating-point accumulation: 0.5 stepping is fine in JS (0.5 → 1.0 → 1.5), but traps like 0.1 + 0.2 !== 0.3 appear with other step values; keeping one decimal place uniformly pins the result back to a clean decimal. getStarFill’s check order is full → half → empty: full stars first, then half, guaranteeing that with value=2.5 the 3rd star is half rather than empty.
getRateAriaText interpolates {n} via formatMessage, with the text template coming from the locale pack (rate.ariaText), and the number is rounded the same way before being turned into a string — the aria text and the visual fill must be based on the same normalized value.
Function table
Section titled “Function table”| Function | Signature | Returns | Purpose |
|---|---|---|---|
clampRate |
(value: number | undefined, max: number) => number |
clamped value | non-numbers become 0, upper bound is max |
stepRate |
(value, direction: 1 | -1, allowHalf, max) => number |
stepped value | half-star 0.5 stepping, one decimal place |
getStarFill |
(value, index, allowHalf) => 'full' | 'half' | 'empty' |
fill tier | the index-th star (1-based) |
getRateAriaText |
(value, template: string) => string |
aria text | interpolates {n} stars |
getRateClassNames |
(state: LhRateState & { readonly, disabled }) => string[] |
class names | readonly/disabled modifiers |
getRateAria |
(options: { value, max, disabled?, ariaValueText? }) => LhAriaAttrs |
aria attribute bag | slider semantics |
Type definitions
Section titled “Type definitions”export interface LhRateState { max: number value: number allowHalf: boolean}
export const LH_RATE_MAX_DEFAULT = 5Accessibility
Section titled “Accessibility”getRateAria emits slider semantics (not radiogroup): a rate’s value is continuously adjustable (especially in half-star mode), and the APG rating pattern is often implemented as a slider. Keyboard interaction follows the slider convention: ←/→ decreases/increases by one step.
getRateAria({ value: 4.5, max: 5 })// { role: 'slider', 'aria-valuenow': 4.5, 'aria-valuemin': 0, 'aria-valuemax': 5 }
getRateAria({ value: 0, max: 5, disabled: true, ariaValueText: '0 stars' })// { role: 'slider', 'aria-valuenow': 0, 'aria-valuemin': 0, 'aria-valuemax': 5,// 'aria-disabled': true, 'aria-valuetext': '0 stars' }aria-valuetext is announced in preference to valuenow — the screen reader says “4 and a half stars” instead of “4.5.” Each star button additionally needs an aria-label (generated by the UI layer with getRateAriaText(value, t('rate.ariaText'))).
Usage examples
Section titled “Usage examples”import { getStarFill, stepRate, clampRate } from '@lh-ui/share/core/rate'
getStarFill(3.5, 3, true) // 'full'getStarFill(3.5, 4, true) // 'half'getStarFill(3.5, 4, false) // 'empty' (in whole-star mode, the 4th star of 3.5 is empty)getStarFill(3.5, 5, true) // 'empty'stepRate(3, 1, true, 5) // 3.5stepRate(3.5, 1, true, 5) // 4stepRate(0, -1, true, 5) // 0 (clamped)clampRate(undefined, 5) // 0clampRate(9, 5) // 5<!-- Vue3 --><div :class="getRateClassNames({ max, value, allowHalf, readonly, disabled })"> <div class="lh-rate__star-list" v-bind="getRateAria({ value, max, disabled, ariaValueText: getRateAriaText(value, t('rate.ariaText')) })" tabindex="0" @keydown.left="onStep(-1)" @keydown.right="onStep(1)" > <button v-for="i in max" :key="i" type="button" class="lh-rate__star" :class="`lh-rate__star--${getStarFill(value, i, allowHalf)}`" @click="onSelect(i)" > <LhIcon :name="getStarFill(value, i, allowHalf) === 'empty' ? 'star' : 'star-fill'" /> </button> </div></div>// React: in half-star mode, hover state decides the value; on mobile, whole stars are clicked// Stepping: const next = stepRate(value, dir, allowHalf, max); onChange(clampRate(next, max))// uniapp: <view> iterating max stars; aria support is limited on non-h5 targets, and// getRateAria's undefined attributes are simply ignoredStyles and tokens
Section titled “Styles and tokens”| Class | Semantics |
|---|---|
.lh-rate |
Base class |
.lh-rate--readonly |
Readonly (hides the hover highlight, disables the pointer) |
.lh-rate--disabled |
Disabled |
.lh-rate__star-list |
Star group container (tabindex and keyboard events go here) |
.lh-rate__star |
Single star button |
.lh-rate__star--full / --half / --empty |
Fill tiers (half uses a half-occlusion overlay) |
.lh-rate__text |
Score text (like “3.5 points”) |
The star color is --lh-warning-color (yellow); empty stars use --lh-fill-color / --lh-border-color.
Caveats
Section titled “Caveats”getStarFill’s index is 1-based; iterate withv-for="i in max"(inclusive of max) rather thanv-for="i in max - 1".- Consistency between half and whole stars: after
allowHalfchanges,stepRateandgetStarFillmust be passed the same value, otherwise the tiers from clicking and from keyboard stepping will not line up. - Semantics of the readonly state:
readonlyonly switches the class name (visually disabling interaction), andgetRateAriadoes not emitaria-disabled— a readonly rate should still be recognized by screen readers as an understandable rating value (viaaria-valuetext), which distinguishes it from disabled. - The boundary of
max:LH_RATE_MAX_DEFAULTis 5; the UI layer may change max, but too many stars (like 20) overflow on narrow screens, so wrap them yourself. - value is decimal but allowHalf is false: core does not validate this combination;
getStarFilltreats 3.5 as “3 full stars + a 4th empty star” — this is a configuration error by the caller, visually appearing as truncation.
@lh-ui/share/core/rate → getStarFill( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue