mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-16 10:01:59 +08:00
feat(range): add knobMoveStart and knobMoveEnd events (#25011)
This commit is contained in:
@ -1272,6 +1272,8 @@ export class IonRadioGroup {
|
||||
}
|
||||
|
||||
import type { RangeChangeEventDetail as IRangeRangeChangeEventDetail } from '@ionic/core';
|
||||
import type { RangeKnobMoveStartEventDetail as IRangeRangeKnobMoveStartEventDetail } from '@ionic/core';
|
||||
import type { RangeKnobMoveEndEventDetail as IRangeRangeKnobMoveEndEventDetail } from '@ionic/core';
|
||||
export declare interface IonRange extends Components.IonRange {
|
||||
/**
|
||||
* Emitted when the value property has changed.
|
||||
@ -1285,6 +1287,16 @@ export declare interface IonRange extends Components.IonRange {
|
||||
* Emitted when the range loses focus.
|
||||
*/
|
||||
ionBlur: EventEmitter<CustomEvent<void>>;
|
||||
/**
|
||||
* Emitted when the user starts moving the range knob, whether through
|
||||
mouse drag, touch gesture, or keyboard interaction.
|
||||
*/
|
||||
ionKnobMoveStart: EventEmitter<CustomEvent<IRangeRangeKnobMoveStartEventDetail>>;
|
||||
/**
|
||||
* Emitted when the user finishes moving the range knob, whether through
|
||||
mouse drag, touch gesture, or keyboard interaction.
|
||||
*/
|
||||
ionKnobMoveEnd: EventEmitter<CustomEvent<IRangeRangeKnobMoveEndEventDetail>>;
|
||||
|
||||
}
|
||||
|
||||
@ -1303,7 +1315,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']);
|
||||
proxyOutputs(this, this.el, ['ionChange', 'ionFocus', 'ionBlur', 'ionKnobMoveStart', 'ionKnobMoveEnd']);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -984,6 +984,8 @@ ion-range,prop,value,number | { lower: number; upper: number; },0,false,false
|
||||
ion-range,event,ionBlur,void,true
|
||||
ion-range,event,ionChange,RangeChangeEventDetail,true
|
||||
ion-range,event,ionFocus,void,true
|
||||
ion-range,event,ionKnobMoveEnd,RangeKnobMoveEndEventDetail,true
|
||||
ion-range,event,ionKnobMoveStart,RangeKnobMoveStartEventDetail,true
|
||||
ion-range,css-prop,--bar-background
|
||||
ion-range,css-prop,--bar-background-active
|
||||
ion-range,css-prop,--bar-border-radius
|
||||
|
10
core/src/components.d.ts
vendored
10
core/src/components.d.ts
vendored
@ -5,7 +5,7 @@
|
||||
* It contains typing information for all components that exist in this project.
|
||||
*/
|
||||
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
|
||||
import { AccordionGroupChangeEventDetail, ActionSheetAttributes, ActionSheetButton, AlertButton, AlertInput, AnimationBuilder, AutocompleteTypes, BreadcrumbCollapsedClickEventDetail, CheckboxChangeEventDetail, Color, ComponentProps, ComponentRef, DatetimeChangeEventDetail, DomRenderFn, FooterHeightFn, FrameworkDelegate, HeaderFn, HeaderHeightFn, InputChangeEventDetail, ItemHeightFn, ItemRenderFn, ItemReorderEventDetail, LoadingAttributes, MenuChangeEventDetail, ModalAttributes, ModalBreakpointChangeEventDetail, NavComponent, NavComponentWithProps, NavOptions, OverlayEventDetail, PickerAttributes, PickerButton, PickerColumn, PopoverAttributes, PopoverSize, PositionAlign, PositionReference, PositionSide, RadioGroupChangeEventDetail, RangeChangeEventDetail, RangeValue, RefresherEventDetail, RouteID, RouterDirection, RouterEventDetail, RouterOutletOptions, RouteWrite, ScrollBaseDetail, ScrollDetail, SearchbarChangeEventDetail, SegmentButtonLayout, SegmentChangeEventDetail, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, Side, SpinnerTypes, StyleEventDetail, SwipeGestureHandler, TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout, TextareaChangeEventDetail, TextFieldTypes, ToastButton, ToggleChangeEventDetail, TransitionDoneFn, TransitionInstruction, TriggerAction, ViewController } from "./interface";
|
||||
import { AccordionGroupChangeEventDetail, ActionSheetAttributes, ActionSheetButton, AlertButton, AlertInput, AnimationBuilder, AutocompleteTypes, BreadcrumbCollapsedClickEventDetail, CheckboxChangeEventDetail, Color, ComponentProps, ComponentRef, DatetimeChangeEventDetail, DomRenderFn, FooterHeightFn, FrameworkDelegate, HeaderFn, HeaderHeightFn, InputChangeEventDetail, ItemHeightFn, ItemRenderFn, ItemReorderEventDetail, LoadingAttributes, MenuChangeEventDetail, ModalAttributes, ModalBreakpointChangeEventDetail, NavComponent, NavComponentWithProps, NavOptions, OverlayEventDetail, PickerAttributes, PickerButton, PickerColumn, PopoverAttributes, PopoverSize, PositionAlign, PositionReference, PositionSide, RadioGroupChangeEventDetail, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue, RefresherEventDetail, RouteID, RouterDirection, RouterEventDetail, RouterOutletOptions, RouteWrite, ScrollBaseDetail, ScrollDetail, SearchbarChangeEventDetail, SegmentButtonLayout, SegmentChangeEventDetail, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, Side, SpinnerTypes, StyleEventDetail, SwipeGestureHandler, TabBarChangedEventDetail, TabButtonClickEventDetail, TabButtonLayout, TextareaChangeEventDetail, TextFieldTypes, ToastButton, ToggleChangeEventDetail, TransitionDoneFn, TransitionInstruction, TriggerAction, ViewController } from "./interface";
|
||||
import { IonicSafeString } from "./utils/sanitization";
|
||||
import { AlertAttributes } from "./components/alert/alert-interface";
|
||||
import { CounterFormatter } from "./components/item/item-interface";
|
||||
@ -5771,6 +5771,14 @@ declare namespace LocalJSX {
|
||||
* Emitted when the range has focus.
|
||||
*/
|
||||
"onIonFocus"?: (event: CustomEvent<void>) => void;
|
||||
/**
|
||||
* Emitted when the user finishes moving the range knob, whether through mouse drag, touch gesture, or keyboard interaction.
|
||||
*/
|
||||
"onIonKnobMoveEnd"?: (event: CustomEvent<RangeKnobMoveEndEventDetail>) => void;
|
||||
/**
|
||||
* Emitted when the user starts moving the range knob, whether through mouse drag, touch gesture, or keyboard interaction.
|
||||
*/
|
||||
"onIonKnobMoveStart"?: (event: CustomEvent<RangeKnobMoveStartEventDetail>) => void;
|
||||
/**
|
||||
* Emitted when the styles change.
|
||||
*/
|
||||
|
@ -8,7 +8,15 @@ export interface RangeChangeEventDetail {
|
||||
value: RangeValue;
|
||||
}
|
||||
|
||||
export interface RangeKnobMoveStartEventDetail {
|
||||
value: RangeValue;
|
||||
}
|
||||
|
||||
export interface RangeKnobMoveEndEventDetail {
|
||||
value: RangeValue;
|
||||
}
|
||||
|
||||
export interface RangeCustomEvent extends CustomEvent {
|
||||
detail: RangeChangeEventDetail;
|
||||
detail: RangeChangeEventDetail | RangeKnobMoveStartEventDetail | RangeKnobMoveEndEventDetail;
|
||||
target: HTMLIonRangeElement;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop, State, Watch, h } from '@stencil/core';
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { Color, Gesture, GestureDetail, KnobName, RangeChangeEventDetail, RangeValue, StyleEventDetail } from '../../interface';
|
||||
import { Color, Gesture, GestureDetail, KnobName, RangeChangeEventDetail, RangeKnobMoveEndEventDetail, RangeKnobMoveStartEventDetail, RangeValue, StyleEventDetail } from '../../interface';
|
||||
import { Attributes, clamp, debounceEvent, getAriaLabel, inheritAttributes, renderHiddenInput } from '../../utils/helpers';
|
||||
import { isRTL } from '../../utils/rtl';
|
||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
@ -191,6 +191,18 @@ export class Range implements ComponentInterface {
|
||||
*/
|
||||
@Event() ionBlur!: EventEmitter<void>;
|
||||
|
||||
/**
|
||||
* Emitted when the user starts moving the range knob, whether through
|
||||
* mouse drag, touch gesture, or keyboard interaction.
|
||||
*/
|
||||
@Event() ionKnobMoveStart!: EventEmitter<RangeKnobMoveStartEventDetail>;
|
||||
|
||||
/**
|
||||
* Emitted when the user finishes moving the range knob, whether through
|
||||
* mouse drag, touch gesture, or keyboard interaction.
|
||||
*/
|
||||
@Event() ionKnobMoveEnd!: EventEmitter<RangeKnobMoveEndEventDetail>;
|
||||
|
||||
private setupGesture = async () => {
|
||||
const rangeSlider = this.rangeSlider;
|
||||
if (rangeSlider) {
|
||||
@ -246,6 +258,8 @@ export class Range implements ComponentInterface {
|
||||
}
|
||||
|
||||
private handleKeyboard = (knob: KnobName, isIncrease: boolean) => {
|
||||
const { ensureValueInBounds } = this;
|
||||
|
||||
let step = this.step;
|
||||
step = step > 0 ? step : 1;
|
||||
step = step / (this.max - this.min);
|
||||
@ -257,7 +271,10 @@ export class Range implements ComponentInterface {
|
||||
} else {
|
||||
this.ratioB = clamp(0, this.ratioB + step, 1);
|
||||
}
|
||||
|
||||
this.ionKnobMoveStart.emit({ value: ensureValueInBounds(this.value) });
|
||||
this.updateValue();
|
||||
this.ionKnobMoveEnd.emit({ value: ensureValueInBounds(this.value) });
|
||||
}
|
||||
|
||||
private getValue(): RangeValue {
|
||||
@ -305,6 +322,8 @@ export class Range implements ComponentInterface {
|
||||
|
||||
// update the active knob's position
|
||||
this.update(currentX);
|
||||
|
||||
this.ionKnobMoveStart.emit({ value: this.ensureValueInBounds(this.value) });
|
||||
}
|
||||
|
||||
private onMove(detail: GestureDetail) {
|
||||
@ -314,6 +333,8 @@ export class Range implements ComponentInterface {
|
||||
private onEnd(detail: GestureDetail) {
|
||||
this.update(detail.currentX);
|
||||
this.pressedKnob = undefined;
|
||||
|
||||
this.ionKnobMoveEnd.emit({ value: this.ensureValueInBounds(this.value) });
|
||||
}
|
||||
|
||||
private update(currentX: number) {
|
||||
|
@ -25,6 +25,22 @@ interface RangeChangeEventDetail {
|
||||
}
|
||||
```
|
||||
|
||||
### RangeKnobMoveStartEventDetail
|
||||
|
||||
```typescript
|
||||
interface RangeKnobMoveStartEventDetail {
|
||||
value: RangeValue;
|
||||
}
|
||||
```
|
||||
|
||||
### RangeKnobMoveEndEventDetail
|
||||
|
||||
```typescript
|
||||
interface RangeKnobMoveEndEventDetail {
|
||||
value: RangeValue;
|
||||
}
|
||||
```
|
||||
|
||||
### RangeCustomEvent
|
||||
|
||||
While not required, this interface can be used in place of the `CustomEvent` interface for stronger typing with Ionic events emitted from this component.
|
||||
@ -369,10 +385,12 @@ export default defineComponent({
|
||||
## Events
|
||||
|
||||
| Event | Description | Type |
|
||||
| ----------- | -------------------------------------------- | ------------------------------------- |
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------- |
|
||||
| `ionBlur` | Emitted when the range loses focus. | `CustomEvent<void>` |
|
||||
| `ionChange` | Emitted when the value property has changed. | `CustomEvent<RangeChangeEventDetail>` |
|
||||
| `ionFocus` | Emitted when the range has focus. | `CustomEvent<void>` |
|
||||
| `ionKnobMoveEnd` | Emitted when the user finishes moving the range knob, whether through mouse drag, touch gesture, or keyboard interaction. | `CustomEvent<RangeKnobMoveEndEventDetail>` |
|
||||
| `ionKnobMoveStart` | Emitted when the user starts moving the range knob, whether through mouse drag, touch gesture, or keyboard interaction. | `CustomEvent<RangeKnobMoveStartEventDetail>` |
|
||||
|
||||
|
||||
## Slots
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
import { dragElementBy } from '@utils/test';
|
||||
|
||||
test('range: basic', async () => {
|
||||
const page = await newE2EPage({
|
||||
@ -17,3 +18,45 @@ test('range:rtl: basic', async () => {
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
|
||||
test('range: start/end events', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/range/test/basic?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const rangeStart = await page.spyOnEvent('ionKnobMoveStart');
|
||||
const rangeEnd = await page.spyOnEvent('ionKnobMoveEnd');
|
||||
const rangeEl = await page.$('#basic');
|
||||
|
||||
await dragElementBy(rangeEl, page, 300, 0);
|
||||
|
||||
/**
|
||||
* dragElementBy defaults to starting the drag from the middle of the el,
|
||||
* so the start value should jump to 50 despite the range defaulting to 20.
|
||||
*/
|
||||
expect(rangeStart).toHaveReceivedEventDetail({ value: 50 });
|
||||
expect(rangeEnd).toHaveReceivedEventDetail({ value: 91 });
|
||||
|
||||
/**
|
||||
* Verify both events fire if range is clicked without dragging.
|
||||
*/
|
||||
await dragElementBy(rangeEl, page, 0, 0);
|
||||
|
||||
expect(rangeStart).toHaveReceivedEventDetail({ value: 50 });
|
||||
expect(rangeEnd).toHaveReceivedEventDetail({ value: 50 })
|
||||
});
|
||||
|
||||
test('range: start/end events, keyboard', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/range/test/basic?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const rangeStart = await page.spyOnEvent('ionKnobMoveStart');
|
||||
const rangeEnd = await page.spyOnEvent('ionKnobMoveEnd');
|
||||
|
||||
await page.keyboard.press('Tab'); // focus first range
|
||||
await page.keyboard.press('ArrowRight');
|
||||
|
||||
expect(rangeStart).toHaveReceivedEventDetail({ value: 20 });
|
||||
expect(rangeEnd).toHaveReceivedEventDetail({ value: 21 });
|
||||
});
|
@ -66,7 +66,7 @@
|
||||
</ion-label>
|
||||
</ion-list-header>
|
||||
<ion-item>
|
||||
<ion-range value="20" aria-label="Default Range"></ion-range>
|
||||
<ion-range id="basic" value="20" aria-label="Default Range"></ion-range>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-range value="60" color="light" step="10" pin="true" aria-label="Light Pin Range"></ion-range>
|
||||
@ -240,19 +240,7 @@
|
||||
knob.value = {
|
||||
lower: 33,
|
||||
upper: 60
|
||||
}
|
||||
knob.addEventListener('ionFocus', function (ev) {
|
||||
console.log('focus', ev)
|
||||
})
|
||||
knob.addEventListener('ionBlur', function (ev) {
|
||||
console.log('blur', ev)
|
||||
})
|
||||
knob.addEventListener('ionChange', function (ev) {
|
||||
console.log('change', ev)
|
||||
})
|
||||
debounceRange.addEventListener('ionChange', function (ev) {
|
||||
console.log('change', ev)
|
||||
})
|
||||
};
|
||||
|
||||
function elTest() {
|
||||
var range = document.getElementById('range');
|
||||
|
@ -591,7 +591,9 @@ export const IonRange = /*@__PURE__*/ defineContainer<JSX.IonRange>('ion-range',
|
||||
'ionChange',
|
||||
'ionStyle',
|
||||
'ionFocus',
|
||||
'ionBlur'
|
||||
'ionBlur',
|
||||
'ionKnobMoveStart',
|
||||
'ionKnobMoveEnd'
|
||||
],
|
||||
'value', 'v-ion-change', 'ionChange');
|
||||
|
||||
|
Reference in New Issue
Block a user