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({
|
@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({
|
@Component({
|
||||||
selector: 'ion-range',
|
selector: 'ion-range',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
template: '<ng-content></ng-content>',
|
template: '<ng-content></ng-content>',
|
||||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
// 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 {
|
export class IonRange {
|
||||||
protected el: HTMLElement;
|
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,debounce,number | undefined,undefined,false,false
|
||||||
ion-range,prop,disabled,boolean,false,false,false
|
ion-range,prop,disabled,boolean,false,false,false
|
||||||
ion-range,prop,dualKnobs,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,labelPlacement,"end" | "fixed" | "start",'start',false,false
|
||||||
ion-range,prop,legacy,boolean | undefined,undefined,false,false
|
ion-range,prop,legacy,boolean | undefined,undefined,false,false
|
||||||
ion-range,prop,max,number,100,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.
|
* Show two knobs.
|
||||||
*/
|
*/
|
||||||
"dualKnobs": boolean;
|
"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 ("...").
|
* 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.
|
* Show two knobs.
|
||||||
*/
|
*/
|
||||||
"dualKnobs"?: boolean;
|
"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 ("...").
|
* 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;
|
font-size: $range-md-pin-font-size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO FW-2997 Remove this
|
::slotted([slot="label"]), .label-text {
|
||||||
::slotted([slot="label"]) {
|
|
||||||
font-size: initial;
|
font-size: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@
|
|||||||
opacity: 0.3;
|
opacity: 0.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
::slotted([slot="label"]) {
|
::slotted([slot="label"]), .label-text {
|
||||||
/**
|
/**
|
||||||
* Label text should not extend
|
* Label text should not extend
|
||||||
* beyond the bounds of the range.
|
* beyond the bounds of the range.
|
||||||
|
@ -96,6 +96,13 @@ export class Range implements ComponentInterface {
|
|||||||
*/
|
*/
|
||||||
@Prop() name = this.rangeId;
|
@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.
|
* Show two knobs.
|
||||||
*/
|
*/
|
||||||
@ -602,7 +609,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderRange() {
|
private renderRange() {
|
||||||
const { disabled, el, rangeId, pin, pressedKnob, labelPlacement } = this;
|
const { disabled, el, rangeId, pin, pressedKnob, labelPlacement, label } = this;
|
||||||
|
|
||||||
const mode = getIonMode(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,
|
'label-text-wrapper-hidden': !this.hasLabel,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<slot name="label"></slot>
|
{label !== undefined ? <div class="label-text">{label}</div> : <slot name="label"></slot>}
|
||||||
</div>
|
</div>
|
||||||
<div class="native-wrapper">
|
<div class="native-wrapper">
|
||||||
<slot name="start"></slot>
|
<slot name="start"></slot>
|
||||||
@ -642,7 +649,7 @@ Developers can dismiss this warning by removing their usage of the "legacy" prop
|
|||||||
}
|
}
|
||||||
|
|
||||||
private get hasLabel() {
|
private get hasLabel() {
|
||||||
return this.el.querySelector('[slot="label"]') !== null;
|
return this.label !== undefined || this.el.querySelector('[slot="label"]') !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderRangeSlider() {
|
private renderRangeSlider() {
|
||||||
|
@ -15,14 +15,18 @@
|
|||||||
<main>
|
<main>
|
||||||
<h1>Range - a11y</h1>
|
<h1>Range - a11y</h1>
|
||||||
|
|
||||||
<ion-range><span slot="label">my label</span></ion-range
|
<ion-range><span slot="label">my label</span></ion-range>
|
||||||
><br />
|
<br />
|
||||||
<ion-range aria-label="my aria label"></ion-range><br />
|
<ion-range label="my label"></ion-range>
|
||||||
|
<br />
|
||||||
|
<ion-range aria-label="my aria label"></ion-range>
|
||||||
|
<br />
|
||||||
<ion-range>
|
<ion-range>
|
||||||
<span slot="label">temperature</span>
|
<span slot="label">temperature</span>
|
||||||
<ion-icon name="snow" slot="start" aria-hidden="true"></ion-icon>
|
<ion-icon name="snow" slot="start" aria-hidden="true"></ion-icon>
|
||||||
<ion-icon name="flame" slot="end" aria-hidden="true"></ion-icon> </ion-range
|
<ion-icon name="flame" slot="end" aria-hidden="true"></ion-icon>
|
||||||
><br />
|
</ion-range>
|
||||||
|
<br />
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -67,6 +67,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
<h1>Slotted Items</h1>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="grid-item">
|
<div class="grid-item">
|
||||||
|
@ -125,6 +125,32 @@ configs().forEach(({ title, screenshot, config }) => {
|
|||||||
expect(await range.screenshot()).toMatchSnapshot(screenshot(`range-items-fixed`));
|
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',
|
'color',
|
||||||
'debounce',
|
'debounce',
|
||||||
'name',
|
'name',
|
||||||
|
'label',
|
||||||
'dualKnobs',
|
'dualKnobs',
|
||||||
'min',
|
'min',
|
||||||
'max',
|
'max',
|
||||||
|