diff --git a/angular/src/directives/proxies.ts b/angular/src/directives/proxies.ts index e7f23a40025..06f9019ccda 100644 --- a/angular/src/directives/proxies.ts +++ b/angular/src/directives/proxies.ts @@ -1304,13 +1304,13 @@ mouse drag, touch gesture, or keyboard interaction. @ProxyCmp({ defineCustomElementFn: undefined, - inputs: ['color', 'debounce', 'disabled', 'dualKnobs', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value'] + inputs: ['activeBarStart', 'color', 'debounce', 'disabled', 'dualKnobs', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value'] }) @Component({ selector: 'ion-range', changeDetection: ChangeDetectionStrategy.OnPush, template: '', - inputs: ['color', 'debounce', 'disabled', 'dualKnobs', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value'] + inputs: ['activeBarStart', 'color', 'debounce', 'disabled', 'dualKnobs', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value'] }) export class IonRange { protected el: HTMLElement; diff --git a/core/api.txt b/core/api.txt index 4ba332dafff..a844e4c7432 100644 --- a/core/api.txt +++ b/core/api.txt @@ -971,6 +971,7 @@ ion-radio-group,prop,value,any,undefined,false,false ion-radio-group,event,ionChange,RadioGroupChangeEventDetail,true ion-range,shadow +ion-range,prop,activeBarStart,number | undefined,undefined,false,false ion-range,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-range,prop,debounce,number,0,false,false ion-range,prop,disabled,boolean,false,false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index ef595095875..66c0c6e50da 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -2076,6 +2076,10 @@ export namespace Components { "value"?: any | null; } interface IonRange { + /** + * The start position of the range active bar. This feature is only available with a single knob (dualKnobs="false"). Valid values are greater than or equal to the min value and less than or equal to the max value. + */ + "activeBarStart"?: number; /** * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). */ @@ -5961,6 +5965,10 @@ declare namespace LocalJSX { "value"?: any | null; } interface IonRange { + /** + * The start position of the range active bar. This feature is only available with a single knob (dualKnobs="false"). Valid values are greater than or equal to the min value and less than or equal to the max value. + */ + "activeBarStart"?: number; /** * The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). */ diff --git a/core/src/components/range/range.tsx b/core/src/components/range/range.tsx index 5bf61d19c87..f97ce9c5d1d 100644 --- a/core/src/components/range/range.tsx +++ b/core/src/components/range/range.tsx @@ -16,6 +16,7 @@ import type { import { findClosestIonContent, disableContentScrollY, resetContentScrollY } from '../../utils/content'; import type { Attributes } from '../../utils/helpers'; import { inheritAriaAttributes, clamp, debounceEvent, getAriaLabel, renderHiddenInput } from '../../utils/helpers'; +import { printIonWarning } from '../../utils/logging'; import { isRTL } from '../../utils/rtl'; import { createColorClasses, hostContext } from '../../utils/theme'; @@ -142,6 +143,31 @@ export class Range implements ComponentInterface { */ @Prop() ticks = true; + /** + * The start position of the range active bar. This feature is only available with a single knob (dualKnobs="false"). + * Valid values are greater than or equal to the min value and less than or equal to the max value. + */ + @Prop({ mutable: true }) activeBarStart?: number; + @Watch('activeBarStart') + protected activeBarStartChanged() { + const { activeBarStart } = this; + if (activeBarStart !== undefined) { + if (activeBarStart > this.max) { + printIonWarning( + `Range: The value of activeBarStart (${activeBarStart}) is greater than the max (${this.max}). Valid values are greater than or equal to the min value and less than or equal to the max value.`, + this.el + ); + this.activeBarStart = this.max; + } else if (activeBarStart < this.min) { + printIonWarning( + `Range: The value of activeBarStart (${activeBarStart}) is less than the min (${this.min}). Valid values are greater than or equal to the min value and less than or equal to the max value.`, + this.el + ); + this.activeBarStart = this.min; + } + } + } + /** * If `true`, the user cannot interact with the range. */ @@ -252,6 +278,7 @@ export class Range implements ComponentInterface { this.updateRatio(); this.debounceChanged(); this.disabledChanged(); + this.activeBarStartChanged(); /** * If we have not yet rendered @@ -395,7 +422,11 @@ export class Range implements ComponentInterface { if (this.dualKnobs) { return Math.min(this.ratioA, this.ratioB); } - return 0; + const { activeBarStart } = this; + if (activeBarStart == null) { + return 0; + } + return valueToRatio(activeBarStart, this.min, this.max); } private get ratioUpper() { @@ -484,8 +515,8 @@ export class Range implements ComponentInterface { labelText = inheritedAttributes['aria-label']; } const mode = getIonMode(this); - const barStart = `${ratioLower * 100}%`; - const barEnd = `${100 - ratioUpper * 100}%`; + let barStart = `${ratioLower * 100}%`; + let barEnd = `${100 - ratioUpper * 100}%`; const rtl = isRTL(this.el); @@ -498,6 +529,33 @@ export class Range implements ComponentInterface { }; }; + if (this.dualKnobs === false) { + /** + * When the value is less than the activeBarStart or the min value, + * the knob will display at the start of the active bar. + */ + if (this.valA < (this.activeBarStart ?? this.min)) { + /** + * Sets the bar positions relative to the upper and lower limits. + * Converts the ratio values into percentages, used as offsets for left/right styles. + * + * The ratioUpper refers to the knob position on the bar. + * The ratioLower refers to the end position of the active bar (the value). + */ + barStart = `${ratioUpper * 100}%`; + barEnd = `${100 - ratioLower * 100}%`; + } else { + /** + * Otherwise, the knob will display at the end of the active bar. + * + * The ratioLower refers to the start position of the active bar (the value). + * The ratioUpper refers to the knob position on the bar. + */ + barStart = `${ratioLower * 100}%`; + barEnd = `${100 - ratioUpper * 100}%`; + } + } + const barStyle = { [start]: barStart, [end]: barEnd, @@ -508,9 +566,16 @@ export class Range implements ComponentInterface { for (let value = min; value <= max; value += step) { const ratio = valueToRatio(value, min, max); + const ratioMin = Math.min(ratioLower, ratioUpper); + const ratioMax = Math.max(ratioLower, ratioUpper); + const tick: any = { ratio, - active: ratio >= ratioLower && ratio <= ratioUpper, + /** + * Sets the tick mark as active when the tick is between the min bounds and the knob. + * When using activeBarStart, the tick mark will be active between the knob and activeBarStart. + */ + active: ratio >= ratioMin && ratio <= ratioMax, }; tick[start] = `${ratio * 100}%`; diff --git a/core/src/components/range/test/activeBarStart/index.html b/core/src/components/range/test/activeBarStart/index.html new file mode 100644 index 00000000000..a34e159cae6 --- /dev/null +++ b/core/src/components/range/test/activeBarStart/index.html @@ -0,0 +1,148 @@ + + + + + Range - activeBarStart + + + + + + + + + + + + + + + + Range - activeBarStart + + + + + + activeBarStart is 0 and value is 0. + + + + + activeBarStart is 0 and value is 70. + + + + + activeBarStart is 0 and value is -70. + + + + + activeBarStart is -30 and value is 0. + + + + + activeBarStart is 30 and value is 0. + + + + + activeBarStart is between steps + + + + invalid activeBarStart value (less than min) + + + + + invalid activeBarStart value (greater than max) + + + + + activeBarStart is ignored with dual knobs enabled. + + + + + + + diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts b/core/src/components/range/test/activeBarStart/range.e2e.ts new file mode 100644 index 00000000000..b3eb72e0be1 --- /dev/null +++ b/core/src/components/range/test/activeBarStart/range.e2e.ts @@ -0,0 +1,12 @@ +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; + +test.describe('range: activeBarStart', () => { + test('should not have visual regressions', async ({ page }) => { + await page.goto(`/src/components/range/test/activeBarStart`); + + await page.setIonViewport(); + + expect(await page.screenshot()).toMatchSnapshot(`range-activeBarStart-diff-${page.getSnapshotSettings()}.png`); + }); +}); diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..e61f2f80b83 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..060bac98cdc Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..b3fdbdb3ef8 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..062ab1bc903 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..ec3e42f8ad9 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 00000000000..19b99ad6fbb Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..defe7434e9f Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..b565d59dd96 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 00000000000..560e9a3e355 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 00000000000..e0fca449b6e Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 00000000000..7ea1ed52564 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 00000000000..17fbd22d5c1 Binary files /dev/null and b/core/src/components/range/test/activeBarStart/range.e2e.ts-snapshots/range-activeBarStart-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/utils/logging/index.ts b/core/src/utils/logging/index.ts index 45c03cbdcf7..ee4234cb6a5 100644 --- a/core/src/utils/logging/index.ts +++ b/core/src/utils/logging/index.ts @@ -4,8 +4,8 @@ * * @param message - The string message to be logged to the console. */ -export const printIonWarning = (message: string) => { - return console.warn(`[Ionic Warning]: ${message}`); +export const printIonWarning = (message: string, ...params: any[]) => { + return console.warn(`[Ionic Warning]: ${message}`, ...params); }; /* diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index 9371abe3d46..1169996ade4 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -587,6 +587,7 @@ export const IonRange = /*@__PURE__*/ defineContainer('ion-range', 'snaps', 'step', 'ticks', + 'activeBarStart', 'disabled', 'value', 'ionChange',