feat(range): add classes to the range when the value is at the min or max (#30932)

## What is the current behavior?
Range adds classes to the knobs at `min` and `max`, but the host element doesn't reflect those states.

## What is the new behavior?
- Adds `range-value-min` and `range-value-max` when the value is at the `min` or `max`, respectively.
- Adds a spec test verifying the classes are applied properly.

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
This commit is contained in:
Brandy Smith
2026-01-27 14:20:25 -05:00
committed by GitHub
parent 66e1dc0e70
commit fac1a6673c
2 changed files with 108 additions and 1 deletions

View File

@@ -883,7 +883,7 @@ export class Range implements ComponentInterface {
}
render() {
const { disabled, el, hasLabel, rangeId, pin, pressedKnob, labelPlacement, label } = this;
const { disabled, el, hasLabel, rangeId, pin, pressedKnob, labelPlacement, label, dualKnobs, min, max } = this;
const inItem = hostContext('ion-item', el);
@@ -906,6 +906,13 @@ export class Range implements ComponentInterface {
const mode = getIonMode(this);
/**
* Determine if any knob is at the min or max value to
* apply Host classes for styling.
*/
const valueAtMin = dualKnobs ? this.valA === min || this.valB === min : this.valA === min;
const valueAtMax = dualKnobs ? this.valA === max || this.valB === max : this.valA === max;
renderHiddenInput(true, el, this.name, JSON.stringify(this.getValue()), disabled);
return (
@@ -922,6 +929,8 @@ export class Range implements ComponentInterface {
[`range-label-placement-${labelPlacement}`]: true,
'range-item-start-adjustment': needsStartAdjustment,
'range-item-end-adjustment': needsEndAdjustment,
'range-value-min': valueAtMin,
'range-value-max': valueAtMax,
})}
>
<label class="range-wrapper" id="range-label">

View File

@@ -253,3 +253,101 @@ describe('range: item adjustments', () => {
});
});
});
describe('range: value state classes', () => {
it('should apply range-value-min class when value is at min', async () => {
const page = await newSpecPage({
components: [Range],
html: `<ion-range min="0" max="100" value="0"></ion-range>`,
});
const range = page.body.querySelector('ion-range')!;
expect(range.classList.contains('range-value-min')).toBe(true);
expect(range.classList.contains('range-value-max')).toBe(false);
});
it('should apply range-value-max class when value is at max', async () => {
const page = await newSpecPage({
components: [Range],
html: `<ion-range min="0" max="100" value="100"></ion-range>`,
});
const range = page.body.querySelector('ion-range')!;
expect(range.classList.contains('range-value-max')).toBe(true);
expect(range.classList.contains('range-value-min')).toBe(false);
});
it('should not apply range-value-min or range-value-max classes when value is in the middle', async () => {
const page = await newSpecPage({
components: [Range],
html: `<ion-range min="0" max="100" value="50"></ion-range>`,
});
const range = page.body.querySelector('ion-range')!;
expect(range.classList.contains('range-value-min')).toBe(false);
expect(range.classList.contains('range-value-max')).toBe(false);
});
it('should apply range-value-min class when lower knob is at min in dual knobs', async () => {
const page = await newSpecPage({
components: [Range],
html: `<ion-range dual-knobs="true" min="0" max="100"></ion-range>`,
});
const range = page.body.querySelector('ion-range')!;
range.value = { lower: 0, upper: 50 };
await page.waitForChanges();
expect(range.classList.contains('range-value-min')).toBe(true);
expect(range.classList.contains('range-value-max')).toBe(false);
});
it('should apply range-value-max class when upper knob is at max in dual knobs', async () => {
const page = await newSpecPage({
components: [Range],
html: `<ion-range dual-knobs="true" min="0" max="100"></ion-range>`,
});
const range = page.body.querySelector('ion-range')!;
range.value = { lower: 50, upper: 100 };
await page.waitForChanges();
expect(range.classList.contains('range-value-max')).toBe(true);
expect(range.classList.contains('range-value-min')).toBe(false);
});
it('should apply range-value-min and range-value-max classes for dual knobs when both are at boundaries', async () => {
const page = await newSpecPage({
components: [Range],
html: `<ion-range dual-knobs="true" min="0" max="100"></ion-range>`,
});
const range = page.body.querySelector('ion-range')!;
range.value = { lower: 0, upper: 100 };
await page.waitForChanges();
expect(range.classList.contains('range-value-min')).toBe(true);
expect(range.classList.contains('range-value-max')).toBe(true);
});
it('should not apply range-value-min or range-value-max classes for dual knobs when neither is at boundaries', async () => {
const page = await newSpecPage({
components: [Range],
html: `<ion-range dual-knobs="true" min="0" max="100"></ion-range>`,
});
const range = page.body.querySelector('ion-range')!;
range.value = { lower: 25, upper: 75 };
await page.waitForChanges();
expect(range.classList.contains('range-value-min')).toBe(false);
expect(range.classList.contains('range-value-max')).toBe(false);
});
});