Skip to content

Commit

Permalink
feat(range): ionChange will only emit from user committed changes (#2…
Browse files Browse the repository at this point in the history
  • Loading branch information
sean-perkins committed Oct 24, 2022
1 parent 04ed860 commit d1fb7b0
Show file tree
Hide file tree
Showing 21 changed files with 328 additions and 91 deletions.
28 changes: 17 additions & 11 deletions BREAKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,21 +103,27 @@ Ionic now listens on the `keydown` event instead of the `keyup` event when deter

<h4 id="version-7x-range">Range</h4>

Range is updated to align with the design specification for supported modes.
- Range is updated to align with the design specification for supported modes.

**Design tokens**
**Design tokens**

iOS:

iOS:
| Token | Previous Value | New Value |
| --------------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `--bar-border-radius` | `0px` | `$range-ios-bar-border-radius` (`2px` default) |
| `--knob-size` | `28px` | `$range-ios-knob-width` (`26px` default) |
| `$range-ios-bar-height` | `2px` | `4px` |
| `$range-ios-bar-background-color` | `rgba(var(--ion-text-color-rgb, 0, 0, 0), .1)` | `var(--ion-color-step-900, #e6e6e6)` |
| `$range-ios-knob-box-shadow` | `0 3px 1px rgba(0, 0, 0, .1), 0 4px 8px rgba(0, 0, 0, .13), 0 0 0 1px rgba(0, 0, 0, .02)` | `0px 0.5px 4px rgba(0, 0, 0, 0.12), 0px 6px 13px rgba(0, 0, 0, 0.12)` |
| `$range-ios-knob-width` | `28px` | `26px` |

|Token|Previous Value|New Value|
|-----|--------------|---------|
|`--bar-border-radius`|`0px`|`$range-ios-bar-border-radius` (`2px` default)|
|`--knob-size`|`28px`|`$range-ios-knob-width` (`26px` default)|
|`$range-ios-bar-height`|`2px`|`4px`|
|`$range-ios-bar-background-color`|`rgba(var(--ion-text-color-rgb, 0, 0, 0), .1)`|`var(--ion-color-step-900, #e6e6e6)`|
|`$range-ios-knob-box-shadow`|`0 3px 1px rgba(0, 0, 0, .1), 0 4px 8px rgba(0, 0, 0, .13), 0 0 0 1px rgba(0, 0, 0, .02)`|`0px 0.5px 4px rgba(0, 0, 0, 0.12), 0px 6px 13px rgba(0, 0, 0, 0.12)`|
|`$range-ios-knob-width`|`28px`|`26px`|
- `ionChange` is no longer emitted when the `value` of `ion-range` is modified externally. `ionChange` is only emitted from user committed changes, such as dragging and releasing the range knob or selecting a new value with the keyboard arrows.
- If your application requires immediate feedback based on the user actively dragging the range knob, consider migrating your event listeners to using `ionInput` instead.

- The `debounce` property's value value has changed from `0` to `undefined`. If `debounce` is undefined, the `ionInput` event will fire immediately.

- Range no longer clamps assigned values within bounds. Developers will need to validate the value they are assigning to `ion-range` is within the `min` and `max` bounds when programmatically assigning a value.

<h4 id="version-7x-searchbar">Searchbar</h4>

Expand Down
15 changes: 13 additions & 2 deletions angular/src/directives/proxies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1321,9 +1321,20 @@ import type { RangeKnobMoveStartEventDetail as IRangeRangeKnobMoveStartEventDeta
import type { RangeKnobMoveEndEventDetail as IRangeRangeKnobMoveEndEventDetail } from '@ionic/core';
export declare interface IonRange extends Components.IonRange {
/**
* Emitted when the value property has changed.
* The `ionChange` event is fired for `<ion-range>` elements when the user
modifies the element's value:
- When the user releases the knob after dragging;
- When the user moves the knob with keyboard arrows
`ionChange` is not fired when the value is changed programmatically.
*/
ionChange: EventEmitter<CustomEvent<IRangeRangeChangeEventDetail>>;
/**
* The `ionInput` event is fired for `<ion-range>` elements when the value
is modified. Unlike `ionChange`, `ionInput` is fired continuously
while the user is dragging the knob.
*/
ionInput: EventEmitter<CustomEvent<IRangeRangeChangeEventDetail>>;
/**
* Emitted when the range has focus.
*/
Expand Down Expand Up @@ -1360,7 +1371,7 @@ export class IonRange {
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur', 'ionKnobMoveStart', 'ionKnobMoveEnd']);
proxyOutputs(this, this.el, ['ionChange', 'ionInput', 'ionFocus', 'ionBlur', 'ionKnobMoveStart', 'ionKnobMoveEnd']);
}
}

Expand Down
4 changes: 1 addition & 3 deletions angular/test/apps/ng12/src/app/form/form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export class FormComponent {
input: ['', Validators.required],
input2: ['Default Value'],
checkbox: [false],
range: [5, Validators.min(10)],
}, {
updateOn: typeof (window as any) !== 'undefined' && window.location.hash === '#blur' ? 'blur' : 'change'
});
Expand All @@ -41,8 +40,7 @@ export class FormComponent {
toggle: true,
input: 'Some value',
input2: 'Another values',
checkbox: true,
range: 50
checkbox: true
});
}

Expand Down
4 changes: 1 addition & 3 deletions angular/test/apps/ng13/src/app/form/form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ export class FormComponent {
input: ['', Validators.required],
input2: ['Default Value'],
checkbox: [false],
range: [5, Validators.min(10)],
}, {
updateOn: typeof (window as any) !== 'undefined' && window.location.hash === '#blur' ? 'blur' : 'change'
});
Expand All @@ -41,8 +40,7 @@ export class FormComponent {
toggle: true,
input: 'Some value',
input2: 'Another values',
checkbox: true,
range: 50
checkbox: true
});
}

Expand Down
32 changes: 32 additions & 0 deletions angular/test/base/e2e/src/form-controls/range.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
describe('Form Controls: Range', () => {

beforeEach(() => {
cy.visit('/form-controls/range');
});

it('should have form control initial value', () => {
// Cypress does not support checking numeric values of custom elements
// see: https://github.com/cypress-io/cypress/blob/bf6560691436a5a953f7e03e0ea3de38f3d2a632/packages/driver/src/dom/elements/elementHelpers.ts#L7
cy.get('ion-range').invoke('prop', 'value').should('eq', 5);
});

it('should reflect Ionic form control status classes', () => {
// Control is initially invalid
cy.get('ion-range').should('have.class', 'ion-invalid');
cy.get('ion-range').should('have.class', 'ion-pristine');
cy.get('ion-range').should('have.class', 'ion-untouched');

// Cypress does not support typing unless the element is focusable.
cy.get('ion-range').shadow()
.find('.range-knob-handle')
.click()
.focus()
.type('{rightarrow}'.repeat(5));

cy.get('ion-range').should('have.class', 'ion-valid');
cy.get('ion-range').should('have.class', 'ion-dirty');
cy.get('ion-range').should('have.class', 'ion-touched');
cy.get('ion-range').invoke('prop', 'value').should('eq', 10);
});

});
21 changes: 6 additions & 15 deletions angular/test/base/e2e/src/form.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ describe('Form', () => {
toggle: false,
input: '',
input2: 'Default Value',
checkbox: false,
range: 5
checkbox: false
});
});

Expand All @@ -51,9 +50,6 @@ describe('Form', () => {
// Click confirm button
cy.get('ion-alert .alert-button:not(.alert-button-role-cancel)').click();

testStatus('INVALID');

cy.get('ion-range').invoke('prop', 'value', 40);
testStatus('VALID');

testData({
Expand All @@ -62,8 +58,7 @@ describe('Form', () => {
toggle: false,
input: 'Some value',
input2: 'Default Value',
checkbox: false,
range: 40
checkbox: false
});
});

Expand All @@ -75,8 +70,7 @@ describe('Form', () => {
toggle: true,
input: '',
input2: 'Default Value',
checkbox: false,
range: 5
checkbox: false
});
});

Expand All @@ -88,8 +82,7 @@ describe('Form', () => {
toggle: false,
input: '',
input2: 'Default Value',
checkbox: true,
range: 5
checkbox: true
});
});

Expand All @@ -109,8 +102,7 @@ describe('Form', () => {
toggle: true,
input: '',
input2: 'Default Value',
checkbox: false,
range: 5
checkbox: false
});
cy.get('ion-checkbox').click();
testData({
Expand All @@ -119,8 +111,7 @@ describe('Form', () => {
toggle: true,
input: '',
input2: 'Default Value',
checkbox: true,
range: 5
checkbox: true
});
});
});
Expand Down
14 changes: 1 addition & 13 deletions angular/test/base/e2e/src/inputs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ describe('Inputs', () => {
cy.get('ion-input').should('have.prop', 'value').and('equal', 'some text');
cy.get('ion-datetime').should('have.prop', 'value').and('equal', '1994-03-15');
cy.get('ion-select').should('have.prop', 'value').and('equal', 'nes');
cy.get('ion-range').should('have.prop', 'value').and('equal', 10);
});

it('should have reset value', () => {
Expand All @@ -20,7 +19,6 @@ describe('Inputs', () => {
cy.get('ion-input').should('have.prop', 'value').and('equal', '');
cy.get('ion-datetime').should('have.prop', 'value').and('equal', '');
cy.get('ion-select').should('have.prop', 'value').and('equal', '');
cy.get('ion-range').should('have.prop', 'value').and('be.NaN');
});

it('should get some value', () => {
Expand All @@ -32,7 +30,6 @@ describe('Inputs', () => {
cy.get('ion-input').should('have.prop', 'value').and('equal', 'some text');
cy.get('ion-datetime').should('have.prop', 'value').and('equal', '1994-03-15');
cy.get('ion-select').should('have.prop', 'value').and('equal', 'nes');
cy.get('ion-range').should('have.prop', 'value').and('equal', 10);
});

it('change values should update angular', () => {
Expand All @@ -54,19 +51,10 @@ describe('Inputs', () => {
// Click confirm button
cy.get('ion-alert .alert-button:not(.alert-button-role-cancel)').click();

cy.get('ion-range').invoke('prop', 'value', 20);

cy.get('#checkbox-note').should('have.text', 'true');
cy.get('#toggle-note').should('have.text', 'true');
cy.get('#input-note').should('have.text', 'hola');
cy.get('#datetime-note').should('have.text', '1994-03-14');
cy.get('#select-note').should('have.text', 'ps');
cy.get('#range-note').should('have.text', '20');
});

it('nested components should not interfere with NgModel', () => {
cy.get('#range-note').should('have.text', '10');
cy.get('#nested-toggle').click();
cy.get('#range-note').should('have.text', '10');
});
})
});
4 changes: 4 additions & 0 deletions angular/test/base/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ const routes: Routes = [
}
]
},
{
path: 'form-controls/range',
loadChildren: () => import('./form-controls/range/range.module').then(m => m.RangeModule)
}
];

@NgModule({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { RangeComponent } from './range.component';

@NgModule({
imports: [
RouterModule.forChild([
{ path: '', component: RangeComponent }
])
]
})
export class RangeRoutingModule { }
16 changes: 16 additions & 0 deletions angular/test/base/src/app/form-controls/range/range.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<ion-header>
<ion-toolbar>
<ion-title>Range</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<form [formGroup]="form">
<ion-list>
<ion-item>
<ion-label>Range</ion-label>
<ion-range formControlName="range" min="0" max="20"></ion-range>
</ion-item>
</ion-list>
<ion-button type="submit">Submit</ion-button>
</form>
</ion-content>
18 changes: 18 additions & 0 deletions angular/test/base/src/app/form-controls/range/range.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
selector: 'app-range',
templateUrl: './range.component.html'
})
export class RangeComponent {

form: FormGroup;

constructor(private fb: FormBuilder) {
this.form = this.fb.group({
range: [5, Validators.min(10)]
});
}

}
19 changes: 19 additions & 0 deletions angular/test/base/src/app/form-controls/range/range.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';

import { RangeRoutingModule } from './range-routing.module';
import { RangeComponent } from './range.component';

@NgModule({
imports: [
FormsModule,
ReactiveFormsModule,
IonicModule,
RangeRoutingModule
],
declarations: [
RangeComponent
]
})
export class RangeModule { }
5 changes: 0 additions & 5 deletions angular/test/base/src/app/form/form.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,6 @@
<ion-checkbox formControlName="checkbox" slot="start"></ion-checkbox>
</ion-item>

<ion-item>
<ion-label>Range</ion-label>
<ion-range formControlName="range"></ion-range>
</ion-item>

</ion-list>
<p>
Form Status: <span id="status">{{ profileForm.status }}</span>
Expand Down
6 changes: 2 additions & 4 deletions angular/test/base/src/app/form/form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ export class FormComponent {
toggle: [false],
input: ['', Validators.required],
input2: ['Default Value'],
checkbox: [false],
range: [5, Validators.min(10)],
checkbox: [false]
}, {
updateOn: typeof (window as any) !== 'undefined' && window.location.hash === '#blur' ? 'blur' : 'change'
});
Expand All @@ -41,8 +40,7 @@ export class FormComponent {
toggle: true,
input: 'Some value',
input2: 'Another values',
checkbox: true,
range: 50
checkbox: true
});
}

Expand Down
14 changes: 0 additions & 14 deletions angular/test/base/src/app/inputs/inputs.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -85,20 +85,6 @@
<ion-note slot="end">{{checkbox}}</ion-note>
</ion-item>

<ion-item>
<ion-label>Range</ion-label>
<ion-range [(ngModel)]="range"></ion-range>
<ion-note slot="end" id="range-note">{{range}}</ion-note>
</ion-item>

<ion-item color="dark">
<ion-label>Range Mirror</ion-label>
<ion-range [(ngModel)]="range">
<ion-toggle slot="start" id="nested-toggle" [(ngModel)]="toggle"></ion-toggle>
</ion-range>
<ion-note slot="end">{{range}}</ion-note>
</ion-item>

</ion-list>
<p>
<ion-button (click)="setValues()" id="set-button">Set values</ion-button>
Expand Down

0 comments on commit d1fb7b0

Please sign in to comment.