fix(range): knob is not cut off in item with modern syntax (#28199)

Issue number: resolves #27199

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

When using the modern range in an item, the knob will get cut off by the
item when the value is at either the min or the max.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Range knob is no longer cut off by the item

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->


This is an extension of
https://github.com/ionic-team/ionic-framework/pull/27188. I decided to
make a separate branch/PR since I added tests and changed the
implementation a bit. Feel free to take all/some/none of this code.

---------

Co-authored-by: Sean Perkins <sean-perkins@users.noreply.github.com>
Co-authored-by: ionitron <hi@ionicframework.com>
This commit is contained in:
Liam DeBeasi
2023-09-19 23:01:50 -04:00
committed by GitHub
parent 3f06da4cfc
commit 0104d89927
58 changed files with 234 additions and 3 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -21,6 +21,14 @@
@include padding($range-ios-padding-vertical, $range-ios-padding-horizontal);
}
:host(.range-item-start-adjustment) {
@include padding(null, null, null, $range-ios-item-padding-horizontal);
}
:host(.range-item-end-adjustment) {
@include padding(null, $range-ios-item-padding-horizontal, null, null);
}
:host(.ion-color) .range-bar-active,
:host(.ion-color) .range-tick-active {
background: current-color(base);

View File

@ -7,8 +7,16 @@
$range-ios-padding-vertical: 8px !default;
/// @prop - Padding start/end of the range
// TODO FW-2997 Remove this
$range-ios-padding-horizontal: 16px !default;
/// @prop - Padding start/end of the range - modern syntax
/**
* 24px was chosen so the knob and its
* shadow do not get cut off by the item.
*/
$range-ios-item-padding-horizontal: 24px !default;
/// @prop - Height of the range slider
$range-ios-slider-height: 42px !default;

View File

@ -30,6 +30,14 @@
@include padding($range-md-padding-vertical, $range-md-padding-horizontal);
}
:host(.range-item-start-adjustment) {
@include padding(null, null, null, $range-md-item-padding-horizontal);
}
:host(.range-item-end-adjustment) {
@include padding(null, $range-md-item-padding-horizontal, null, null);
}
:host(.ion-color) .range-bar {
background: current-color(base, 0.26);
}

View File

@ -7,8 +7,16 @@
$range-md-padding-vertical: 8px !default;
/// @prop - Padding start/end of the range
// TODO FW-2997 Remove this
$range-md-padding-horizontal: 14px !default;
/// @prop - Padding start/end of the range - modern range
/**
* 18px was chosen so the knob and its focus/active
* effects do not get cut off by the item.
*/
$range-md-item-padding-horizontal: 18px !default;
/// @prop - Height of the range slider
$range-md-slider-height: 42px !default;

View File

@ -610,8 +610,41 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
);
}
/**
* Returns true if content was passed to the "start" slot
*/
private get hasStartSlotContent() {
return this.el.querySelector('[slot="start"]') !== null;
}
/**
* Returns true if content was passed to the "end" slot
*/
private get hasEndSlotContent() {
return this.el.querySelector('[slot="end"]') !== null;
}
private renderRange() {
const { disabled, el, rangeId, pin, pressedKnob, labelPlacement, label } = this;
const { disabled, el, hasLabel, rangeId, pin, pressedKnob, labelPlacement, label } = this;
const inItem = hostContext('ion-item', el);
/**
* If there is no start content then the knob at
* the min value will be cut off by the item margin.
*/
const hasStartContent =
(hasLabel && (labelPlacement === 'start' || labelPlacement === 'fixed')) || this.hasStartSlotContent;
const needsStartAdjustment = inItem && !hasStartContent;
/**
* If there is no end content then the knob at
* the max value will be cut off by the item margin.
*/
const hasEndContent = (hasLabel && labelPlacement === 'end') || this.hasEndSlotContent;
const needsEndAdjustment = inItem && !hasEndContent;
const mode = getIonMode(this);
@ -624,18 +657,20 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
id={rangeId}
class={createColorClasses(this.color, {
[mode]: true,
'in-item': hostContext('ion-item', el),
'in-item': inItem,
'range-disabled': disabled,
'range-pressed': pressedKnob !== undefined,
'range-has-pin': pin,
[`range-label-placement-${labelPlacement}`]: true,
'range-item-start-adjustment': needsStartAdjustment,
'range-item-end-adjustment': needsEndAdjustment,
})}
>
<label class="range-wrapper" id="range-label">
<div
class={{
'label-text-wrapper': true,
'label-text-wrapper-hidden': !this.hasLabel,
'label-text-wrapper-hidden': !hasLabel,
}}
>
{label !== undefined ? <div class="label-text">{label}</div> : <slot name="label"></slot>}

View File

@ -31,6 +31,20 @@ configs().forEach(({ title, screenshot, config }) => {
const list = page.locator('ion-list');
await expect(list).toHaveScreenshot(screenshot(`range-inset-list`));
});
test('should render adjustments in item', async ({ page }) => {
await page.setContent(
`
<ion-list inset="true">
<ion-item>
<ion-range value="0" aria-label="true"></ion-range>
</ion-item>
</ion-list>
`,
config
);
const list = page.locator('ion-list');
await expect(list).toHaveScreenshot(screenshot(`range-adjustments`));
});
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 596 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 908 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 589 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 904 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,5 +1,6 @@
import { newSpecPage } from '@stencil/core/testing';
import { Range } from '../range';
import { Item } from '../../item/item';
let sharedRange;
describe('Range', () => {
@ -76,3 +77,152 @@ describe('range id', () => {
expect(range.getAttribute('id')).toBe('my-custom-range');
});
});
describe('range: item adjustments', () => {
it('should add start and end adjustment with no content', async () => {
const page = await newSpecPage({
components: [Item, Range],
html: `
<ion-item>
<ion-range aria-label="Range"></ion-range>
</ion-item>
`,
});
const range = page.body.querySelector('ion-range');
expect(range.classList.contains('range-item-start-adjustment')).toBe(true);
expect(range.classList.contains('range-item-end-adjustment')).toBe(true);
});
it('should add start adjustment with end label and no adornments', async () => {
const page = await newSpecPage({
components: [Item, Range],
html: `
<ion-item>
<ion-range label="Range" label-placement="end"></ion-range>
</ion-item>
`,
});
const range = page.body.querySelector('ion-range');
expect(range.classList.contains('range-item-start-adjustment')).toBe(true);
expect(range.classList.contains('range-item-end-adjustment')).toBe(false);
});
it('should add end adjustment with start label and no adornments', async () => {
const page = await newSpecPage({
components: [Item, Range],
html: `
<ion-item>
<ion-range label="Range" label-placement="start"></ion-range>
</ion-item>
`,
});
const range = page.body.querySelector('ion-range');
expect(range.classList.contains('range-item-start-adjustment')).toBe(false);
expect(range.classList.contains('range-item-end-adjustment')).toBe(true);
});
it('should add end adjustment with fixed label and no adornments', async () => {
const page = await newSpecPage({
components: [Item, Range],
html: `
<ion-item>
<ion-range label="Range" label-placement="fixed"></ion-range>
</ion-item>
`,
});
const range = page.body.querySelector('ion-range');
expect(range.classList.contains('range-item-start-adjustment')).toBe(false);
expect(range.classList.contains('range-item-end-adjustment')).toBe(true);
});
it('should add start adjustment with floating label', async () => {
const page = await newSpecPage({
components: [Item, Range],
html: `
<ion-item>
<ion-range label="Range" label-placement="floating"></ion-range>
</ion-item>
`,
});
const range = page.body.querySelector('ion-range');
expect(range.classList.contains('range-item-start-adjustment')).toBe(true);
expect(range.classList.contains('range-item-end-adjustment')).toBe(true);
});
it('should add start adjustment with stacked label', async () => {
const page = await newSpecPage({
components: [Item, Range],
html: `
<ion-item>
<ion-range label="Range" label-placement="stacked"></ion-range>
</ion-item>
`,
});
const range = page.body.querySelector('ion-range');
expect(range.classList.contains('range-item-start-adjustment')).toBe(true);
expect(range.classList.contains('range-item-end-adjustment')).toBe(true);
});
it('should not add adjustment when not in an item', async () => {
const page = await newSpecPage({
components: [Item, Range],
html: `
<ion-range label="Range" label-placement="stacked"></ion-range>
`,
});
const range = page.body.querySelector('ion-range');
expect(range.classList.contains('range-item-start-adjustment')).toBe(false);
expect(range.classList.contains('range-item-end-adjustment')).toBe(false);
});
// TODO FW-2997 remove this
it('should not add adjustment with legacy syntax', async () => {
const page = await newSpecPage({
components: [Item, Range],
html: `
<ion-range legacy="true"></ion-range>
`,
});
const range = page.body.querySelector('ion-range');
expect(range.classList.contains('range-item-start-adjustment')).toBe(false);
expect(range.classList.contains('range-item-end-adjustment')).toBe(false);
});
it('should not add start adjustment when with start adornment', async () => {
const page = await newSpecPage({
components: [Item, Range],
html: `
<ion-range label="Range" label-placement="end">
<div slot="start">Start Content</div>
</ion-range>
`,
});
const range = page.body.querySelector('ion-range');
expect(range.classList.contains('range-item-start-adjustment')).toBe(false);
expect(range.classList.contains('range-item-end-adjustment')).toBe(false);
});
it('should not add end adjustment when with end adornment', async () => {
const page = await newSpecPage({
components: [Item, Range],
html: `
<ion-range label="Range" label-placement="start">
<div slot="end">Start Content</div>
</ion-range>
`,
});
const range = page.body.querySelector('ion-range');
expect(range.classList.contains('range-item-start-adjustment')).toBe(false);
expect(range.classList.contains('range-item-end-adjustment')).toBe(false);
});
});