feat(range): add label prop (#27408)
Issue number: N/A --------- <!-- 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. --> Labels on `ion-range` can only be set via the `label` slot. When only plain text is needed, this is cumbersome because you need to add an entire new element to wrap the label. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> Label prop added. If both the prop and slot are used, the prop will take priority. ## 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. --> - Docs PR: https://github.com/ionic-team/ionic-docs/pull/2955 - Dev build: `7.0.6-dev.11683657201.139d03f4`
@ -1619,14 +1619,14 @@ export declare interface IonRadioGroup extends Components.IonRadioGroup {
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['activeBarStart', 'color', 'debounce', 'disabled', 'dualKnobs', 'labelPlacement', 'legacy', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value']
|
||||
inputs: ['activeBarStart', 'color', 'debounce', 'disabled', 'dualKnobs', 'label', 'labelPlacement', 'legacy', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value']
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-range',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['activeBarStart', 'color', 'debounce', 'disabled', 'dualKnobs', 'labelPlacement', 'legacy', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value'],
|
||||
inputs: ['activeBarStart', 'color', 'debounce', 'disabled', 'dualKnobs', 'label', 'labelPlacement', 'legacy', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value'],
|
||||
})
|
||||
export class IonRange {
|
||||
protected el: HTMLElement;
|
||||
|
@ -1030,6 +1030,7 @@ ion-range,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secon
|
||||
ion-range,prop,debounce,number | undefined,undefined,false,false
|
||||
ion-range,prop,disabled,boolean,false,false,false
|
||||
ion-range,prop,dualKnobs,boolean,false,false,false
|
||||
ion-range,prop,label,string | undefined,undefined,false,false
|
||||
ion-range,prop,labelPlacement,"end" | "fixed" | "start",'start',false,false
|
||||
ion-range,prop,legacy,boolean | undefined,undefined,false,false
|
||||
ion-range,prop,max,number,100,false,false
|
||||
|
8
core/src/components.d.ts
vendored
@ -2289,6 +2289,10 @@ export namespace Components {
|
||||
* Show two knobs.
|
||||
*/
|
||||
"dualKnobs": boolean;
|
||||
/**
|
||||
* The text to display as the control's label. Use this over the `label` slot if you only need plain text. The `label` property will take priority over the `label` slot if both are used.
|
||||
*/
|
||||
"label"?: string;
|
||||
/**
|
||||
* Where to place the label relative to the range. `"start"`: The label will appear to the left of the range in LTR and to the right in RTL. `"end"`: The label will appear to the right of the range in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
||||
*/
|
||||
@ -6304,6 +6308,10 @@ declare namespace LocalJSX {
|
||||
* Show two knobs.
|
||||
*/
|
||||
"dualKnobs"?: boolean;
|
||||
/**
|
||||
* The text to display as the control's label. Use this over the `label` slot if you only need plain text. The `label` property will take priority over the `label` slot if both are used.
|
||||
*/
|
||||
"label"?: string;
|
||||
/**
|
||||
* Where to place the label relative to the range. `"start"`: The label will appear to the left of the range in LTR and to the right in RTL. `"end"`: The label will appear to the right of the range in LTR and to the left in RTL. `"fixed"`: The label has the same behavior as `"start"` except it also has a fixed width. Long text will be truncated with ellipses ("...").
|
||||
*/
|
||||
|
@ -22,8 +22,7 @@
|
||||
font-size: $range-md-pin-font-size;
|
||||
}
|
||||
|
||||
// TODO FW-2997 Remove this
|
||||
::slotted([slot="label"]) {
|
||||
::slotted([slot="label"]), .label-text {
|
||||
font-size: initial;
|
||||
}
|
||||
|
||||
|
@ -209,7 +209,7 @@
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
::slotted([slot="label"]) {
|
||||
::slotted([slot="label"]), .label-text {
|
||||
/**
|
||||
* Label text should not extend
|
||||
* beyond the bounds of the range.
|
||||
|
@ -96,6 +96,13 @@ export class Range implements ComponentInterface {
|
||||
*/
|
||||
@Prop() name = this.rangeId;
|
||||
|
||||
/**
|
||||
* The text to display as the control's label. Use this over the `label` slot if
|
||||
* you only need plain text. The `label` property will take priority over the
|
||||
* `label` slot if both are used.
|
||||
*/
|
||||
@Prop() label?: string;
|
||||
|
||||
/**
|
||||
* Show two knobs.
|
||||
*/
|
||||
@ -602,7 +609,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
|
||||
}
|
||||
|
||||
private renderRange() {
|
||||
const { disabled, el, rangeId, pin, pressedKnob, labelPlacement } = this;
|
||||
const { disabled, el, rangeId, pin, pressedKnob, labelPlacement, label } = this;
|
||||
|
||||
const mode = getIonMode(this);
|
||||
|
||||
@ -629,7 +636,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
|
||||
'label-text-wrapper-hidden': !this.hasLabel,
|
||||
}}
|
||||
>
|
||||
<slot name="label"></slot>
|
||||
{label !== undefined ? <div class="label-text">{label}</div> : <slot name="label"></slot>}
|
||||
</div>
|
||||
<div class="native-wrapper">
|
||||
<slot name="start"></slot>
|
||||
@ -642,7 +649,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
|
||||
}
|
||||
|
||||
private get hasLabel() {
|
||||
return this.el.querySelector('[slot="label"]') !== null;
|
||||
return this.label !== undefined || this.el.querySelector('[slot="label"]') !== null;
|
||||
}
|
||||
|
||||
private renderRangeSlider() {
|
||||
|
@ -15,14 +15,18 @@
|
||||
<main>
|
||||
<h1>Range - a11y</h1>
|
||||
|
||||
<ion-range><span slot="label">my label</span></ion-range
|
||||
><br />
|
||||
<ion-range aria-label="my aria label"></ion-range><br />
|
||||
<ion-range><span slot="label">my label</span></ion-range>
|
||||
<br />
|
||||
<ion-range label="my label"></ion-range>
|
||||
<br />
|
||||
<ion-range aria-label="my aria label"></ion-range>
|
||||
<br />
|
||||
<ion-range>
|
||||
<span slot="label">temperature</span>
|
||||
<ion-icon name="snow" slot="start" aria-hidden="true"></ion-icon>
|
||||
<ion-icon name="flame" slot="end" aria-hidden="true"></ion-icon> </ion-range
|
||||
><br />
|
||||
<ion-icon name="flame" slot="end" aria-hidden="true"></ion-icon>
|
||||
</ion-range>
|
||||
<br />
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -67,6 +67,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Label Property</h1>
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
<h2>Placement Start</h2>
|
||||
<ion-range label="Temperature"></ion-range>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Placement End</h2>
|
||||
<ion-range label="Temperature" label-placement="end"></ion-range>
|
||||
</div>
|
||||
<div class="grid-item">
|
||||
<h2>Placement Fixed</h2>
|
||||
<ion-range label="Temperature" label-placement="fixed"></ion-range>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1>Slotted Items</h1>
|
||||
<div class="grid">
|
||||
<div class="grid-item">
|
||||
|
@ -125,6 +125,32 @@ configs().forEach(({ title, screenshot, config }) => {
|
||||
expect(await range.screenshot()).toMatchSnapshot(screenshot(`range-items-fixed`));
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('range: label prop', () => {
|
||||
test('should render label in the start placement', async ({ page }) => {
|
||||
await page.setContent(`<ion-range label-placement="start" label="Volume"></ion-range>`, config);
|
||||
|
||||
const range = page.locator('ion-range');
|
||||
|
||||
expect(await range.screenshot()).toMatchSnapshot(screenshot(`range-label-prop-start`));
|
||||
});
|
||||
|
||||
test('should render label in the end placement', async ({ page }) => {
|
||||
await page.setContent(`<ion-range label-placement="end" label="Volume"></ion-range>`, config);
|
||||
|
||||
const range = page.locator('ion-range');
|
||||
|
||||
expect(await range.screenshot()).toMatchSnapshot(screenshot(`range-label-prop-end`));
|
||||
});
|
||||
|
||||
test('should render label in the fixed placement', async ({ page }) => {
|
||||
await page.setContent(`<ion-range label-placement="fixed" label="Volume"></ion-range>`, config);
|
||||
|
||||
const range = page.locator('ion-range');
|
||||
|
||||
expect(await range.screenshot()).toMatchSnapshot(screenshot(`range-label-prop-fixed`));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 8.2 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 8.1 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 5.0 KiB |
23
core/src/components/range/test/label/range.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { newSpecPage } from '@stencil/core/testing';
|
||||
|
||||
import { Range } from '../../range';
|
||||
|
||||
describe('range: label', () => {
|
||||
it('should prioritize the label prop over the slot', async () => {
|
||||
const page = await newSpecPage({
|
||||
components: [Range],
|
||||
html: `
|
||||
<ion-range label="Label prop">
|
||||
<div slot="label">Label slot</div>
|
||||
</ion-range>
|
||||
`,
|
||||
});
|
||||
|
||||
const range = page.body.querySelector('ion-range');
|
||||
const propEl = range?.shadowRoot?.querySelector('.label-text');
|
||||
const slotEl = range?.shadowRoot?.querySelector('slot[name="label"]');
|
||||
|
||||
expect(propEl).not.toBeNull();
|
||||
expect(slotEl).toBeNull();
|
||||
});
|
||||
});
|
@ -606,6 +606,7 @@ export const IonRange = /*@__PURE__*/ defineContainer<JSX.IonRange, JSX.IonRange
|
||||
'color',
|
||||
'debounce',
|
||||
'name',
|
||||
'label',
|
||||
'dualKnobs',
|
||||
'min',
|
||||
'max',
|
||||
|