diff --git a/angular/src/directives/proxies.ts b/angular/src/directives/proxies.ts index e3d4013a7c..279f329ae7 100644 --- a/angular/src/directives/proxies.ts +++ b/angular/src/directives/proxies.ts @@ -1602,13 +1602,13 @@ mouse drag, touch gesture, or keyboard interaction. @ProxyCmp({ defineCustomElementFn: undefined, - inputs: ['activeBarStart', 'color', 'debounce', 'disabled', 'dualKnobs', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value'] + inputs: ['activeBarStart', 'color', 'debounce', 'disabled', 'dualKnobs', 'labelPlacement', 'legacy', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value'] }) @Component({ selector: 'ion-range', changeDetection: ChangeDetectionStrategy.OnPush, template: '', - inputs: ['activeBarStart', 'color', 'debounce', 'disabled', 'dualKnobs', 'max', 'min', 'mode', 'name', 'pin', 'pinFormatter', 'snaps', 'step', 'ticks', 'value'] + inputs: ['activeBarStart', 'color', 'debounce', 'disabled', 'dualKnobs', 'labelPlacement', 'legacy', '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 4b77af2eb3..b15415c3ed 100644 --- a/core/api.txt +++ b/core/api.txt @@ -1025,6 +1025,8 @@ 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,labelPlacement,"end" | "fixed" | "start",'start',false,false +ion-range,prop,legacy,boolean | undefined,undefined,false,false ion-range,prop,max,number,100,false,false ion-range,prop,min,number,0,false,false ion-range,prop,mode,"ios" | "md",undefined,false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 56b37340de..fbda5d12bc 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -2196,6 +2196,14 @@ export namespace Components { * Show two knobs. */ "dualKnobs": boolean; + /** + * 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 ("..."). + */ + "labelPlacement": 'start' | 'end' | 'fixed'; + /** + * Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the `label` property. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup. + */ + "legacy"?: boolean; /** * Maximum integer value of the range. */ @@ -6102,6 +6110,14 @@ declare namespace LocalJSX { * Show two knobs. */ "dualKnobs"?: boolean; + /** + * 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 ("..."). + */ + "labelPlacement"?: 'start' | 'end' | 'fixed'; + /** + * Set the `legacy` property to `true` to forcibly use the legacy form control markup. Ionic will only opt components in to the modern form markup when they are using either the `aria-label` attribute or the `label` property. As a result, the `legacy` property should only be used as an escape hatch when you want to avoid this automatic opt-in behavior. Note that this property will be removed in an upcoming major release of Ionic, and all form components will be opted-in to using the modern form markup. + */ + "legacy"?: boolean; /** * Maximum integer value of the range. */ diff --git a/core/src/components/item/item.scss b/core/src/components/item/item.scss index 68791d0691..3c1663ae18 100644 --- a/core/src/components/item/item.scss +++ b/core/src/components/item/item.scss @@ -107,6 +107,15 @@ overflow: initial; } +/** + * The shadow of the range knob should + * not be clipped by the item. + */ +// TODO FW-2997 This should check for a slotted ion-range +:host(.item-has-modern-range) { + overflow: initial; +} + // Item: Color // -------------------------------------------------- diff --git a/core/src/components/item/item.tsx b/core/src/components/item/item.tsx index 59bdd027ee..41cfdca857 100644 --- a/core/src/components/item/item.tsx +++ b/core/src/components/item/item.tsx @@ -311,8 +311,11 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac } private hasModernInput(): boolean { - const input = this.el.querySelector('ion-input:not(.legacy-input)'); - return input !== null; + return this.el.querySelector('ion-input:not(.legacy-input)') !== null; + } + + private hasModernRange(): boolean { + return this.el.querySelector('ion-range:not(.legacy-range)') !== null; } private getFirstInput(): HTMLIonInputElement | HTMLIonTextareaElement { @@ -401,6 +404,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac const fillValue = fill || 'none'; const inList = hostContext('ion-list', this.el); const hasModernInput = this.hasModernInput(); + const hasModernRange = this.hasModernRange(); return ( - + 0 100 @@ -113,7 +113,7 @@ - + 0 100 diff --git a/core/src/components/range/range.ios.scss b/core/src/components/range/range.ios.scss index 8a5148a29b..f6d11ba151 100644 --- a/core/src/components/range/range.ios.scss +++ b/core/src/components/range/range.ios.scss @@ -14,7 +14,11 @@ --bar-background-active: #{ion-color(primary, base)}; --bar-border-radius: #{$range-ios-bar-border-radius}; --height: #{$range-ios-slider-height}; + --margin: 16px; +} +// TODO FW-2997 remove this +:host(.legacy-range) { @include padding($range-ios-padding-vertical, $range-ios-padding-horizontal); } @@ -24,11 +28,11 @@ } ::slotted([slot="start"]) { - @include margin(0, 16px, 0, 0); + @include margin(0, var(--margin), 0, 0); } ::slotted([slot="end"]) { - @include margin(0, 0, 0, 16px); + @include margin(0, 0, 0, var(--margin)); } :host(.range-has-pin) { diff --git a/core/src/components/range/range.md.scss b/core/src/components/range/range.md.scss index e267271136..35442f2db5 100644 --- a/core/src/components/range/range.md.scss +++ b/core/src/components/range/range.md.scss @@ -16,12 +16,22 @@ --height: #{$range-md-slider-height}; --pin-background: #{ion-color(primary, base)}; --pin-color: #{ion-color(primary, contrast)}; + --margin: 14px; - @include padding($range-md-padding-vertical, $range-md-padding-horizontal); - + // TODO FW-2997 Apply this to the start/end slots, and the native wrapper font-size: $range-md-pin-font-size; } +// TODO FW-2997 Remove this +::slotted([slot="label"]) { + font-size: initial; +} + +// TODO FW-2997 remove this +:host(.legacy-range) { + @include padding($range-md-padding-vertical, $range-md-padding-horizontal); +} + :host(.ion-color) .range-bar { background: current-color(base, 0.26); } @@ -37,11 +47,11 @@ } ::slotted([slot="start"]) { - @include margin(0, 14px, 0, 0); + @include margin(0, var(--margin), 0, 0); } ::slotted([slot="end"]) { - @include margin(0, 0, 0, 14px); + @include margin(0, 0, 0, var(--margin)); } :host(.range-has-pin) { diff --git a/core/src/components/range/range.scss b/core/src/components/range/range.scss index a12919a83e..0c5ffca854 100644 --- a/core/src/components/range/range.scss +++ b/core/src/components/range/range.scss @@ -18,6 +18,7 @@ * @prop --pin-color: Color of the range pin (only available in MD mode) */ --knob-handle-size: calc(var(--knob-size) * 2); + --margin: 8px; display: flex; position: relative; @@ -172,3 +173,140 @@ :host(.in-item) ::slotted(ion-label) { align-self: center; } + +// Range Wrapper +// -------------------------------------------------- + +.range-wrapper { + display: flex; + + position: relative; + + flex-grow: 1; + + align-items: center; + + height: 100%; +} + +// Range Label +// ---------------------------------------------------------------- + +/** + * When the range is disabled, only the text + * receives an opacity. The range changes color instead. + */ +:host(.range-disabled) .label-text-wrapper { + opacity: 0.3; +} + +::slotted([slot="label"]) { + + /** + * Label text should not extend + * beyond the bounds of the range. + * However, we do not set the max + * width to 100% because then + * only the label would show and users + * would not be able to see the range. + */ + max-width: 200px; + + /** + * This ensures that double tapping this text + * clicks the