feat(segment-view): add swipeGesture property to disable swiping (#30948)

Issue number: resolves #30290

---------

## What is the current behavior?
The segment view swipe gesture can only be disabled by adding the `disabled` property and setting `opacity: 1`.

## What is the new behavior?
- Adds a new property, `swipeGesture`, to disable swiping on the segment view
- Adds an e2e test which verifies the styles blocking the swipe are correctly applied when `swipeGesture` is `false`

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

---------

Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
This commit is contained in:
Brandy Smith
2026-02-10 09:59:24 -05:00
committed by GitHub
parent 6e4f60af4c
commit 46806bd6e2
9 changed files with 223 additions and 6 deletions

View File

@@ -1693,6 +1693,7 @@ ion-segment-content,shadow
ion-segment-view,shadow ion-segment-view,shadow
ion-segment-view,prop,disabled,boolean,false,false,false ion-segment-view,prop,disabled,boolean,false,false,false
ion-segment-view,prop,swipeGesture,boolean,true,false,false
ion-segment-view,event,ionSegmentViewScroll,SegmentViewScrollEvent,true ion-segment-view,event,ionSegmentViewScroll,SegmentViewScrollEvent,true
ion-select,shadow ion-select,shadow

View File

@@ -3113,6 +3113,11 @@ export namespace Components {
* @param smoothScroll : Whether to animate the scroll transition. * @param smoothScroll : Whether to animate the scroll transition.
*/ */
"setContent": (id: string, smoothScroll?: boolean) => Promise<void>; "setContent": (id: string, smoothScroll?: boolean) => Promise<void>;
/**
* If `true`, users will be able to swipe the segment view to navigate between segment contents.
* @default true
*/
"swipeGesture": boolean;
} }
interface IonSelect { interface IonSelect {
/** /**
@@ -8424,6 +8429,11 @@ declare namespace LocalJSX {
* Emitted when the segment view is scrolled. * Emitted when the segment view is scrolled.
*/ */
"onIonSegmentViewScroll"?: (event: IonSegmentViewCustomEvent<SegmentViewScrollEvent>) => void; "onIonSegmentViewScroll"?: (event: IonSegmentViewCustomEvent<SegmentViewScrollEvent>) => void;
/**
* If `true`, users will be able to swipe the segment view to navigate between segment contents.
* @default true
*/
"swipeGesture"?: boolean;
} }
interface IonSelect { interface IonSelect {
/** /**

View File

@@ -21,7 +21,8 @@
display: none; display: none;
} }
:host(.segment-view-disabled) { :host(.segment-view-disabled),
:host(.segment-view-swipe-disabled) {
touch-action: none; touch-action: none;
overflow-x: hidden; overflow-x: hidden;
} }

View File

@@ -23,6 +23,11 @@ export class SegmentView implements ComponentInterface {
*/ */
@Prop() disabled = false; @Prop() disabled = false;
/**
* If `true`, users will be able to swipe the segment view to navigate between segment contents.
*/
@Prop() swipeGesture = true;
/** /**
* @internal * @internal
* *
@@ -141,13 +146,14 @@ export class SegmentView implements ComponentInterface {
} }
render() { render() {
const { disabled, isManualScroll } = this; const { disabled, isManualScroll, swipeGesture } = this;
return ( return (
<Host <Host
class={{ class={{
'segment-view-disabled': disabled, 'segment-view-disabled': disabled,
'segment-view-scroll-disabled': isManualScroll === false, 'segment-view-scroll-disabled': isManualScroll === false,
'segment-view-swipe-disabled': swipeGesture === false,
}} }}
> >
<slot></slot> <slot></slot>

View File

@@ -0,0 +1,147 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Segment View - Swipe Gesture</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
<style>
h2 {
font-size: 12px;
font-weight: normal;
color: #6f7378;
margin: 24px 16px 8px;
}
ion-segment-view {
height: 100px;
margin-bottom: 20px;
}
ion-segment-content {
display: flex;
justify-content: center;
align-items: center;
}
ion-segment-content:nth-of-type(3n + 1) {
background: lightpink;
}
ion-segment-content:nth-of-type(3n + 2) {
background: lightblue;
}
ion-segment-content:nth-of-type(3n + 3) {
background: lightgreen;
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Segment View - Swipe Gesture</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<h2>
Swipe Gesture: Segment <ion-text color="success">Enabled</ion-text>; Segment View
<ion-text color="success">Enabled</ion-text>
</h2>
<ion-segment value="free">
<ion-segment-button content-id="paid" value="paid">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button content-id="free" value="free">
<ion-label>Free</ion-label>
</ion-segment-button>
<ion-segment-button content-id="top" value="top">
<ion-label>Top</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="paid">Paid</ion-segment-content>
<ion-segment-content id="free">Free</ion-segment-content>
<ion-segment-content id="top">Top</ion-segment-content>
</ion-segment-view>
<h2>
Swipe Gesture: Segment <ion-text color="danger">Disabled</ion-text>; Segment View
<ion-text color="success">Enabled</ion-text>
</h2>
<ion-segment swipe-gesture="false" value="free2">
<ion-segment-button content-id="paid2" value="paid2">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button content-id="free2" value="free2">
<ion-label>Free</ion-label>
</ion-segment-button>
<ion-segment-button content-id="top2" value="top2">
<ion-label>Top</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="paid2">Paid</ion-segment-content>
<ion-segment-content id="free2">Free</ion-segment-content>
<ion-segment-content id="top2">Top</ion-segment-content>
</ion-segment-view>
<h2>
Swipe Gesture: Segment <ion-text color="success">Enabled</ion-text>; Segment View
<ion-text color="danger">Disabled</ion-text>
</h2>
<ion-segment value="free3">
<ion-segment-button content-id="paid3" value="paid3">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button content-id="free3" value="free3">
<ion-label>Free</ion-label>
</ion-segment-button>
<ion-segment-button content-id="top3" value="top3">
<ion-label>Top</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view swipe-gesture="false">
<ion-segment-content id="paid3">Paid</ion-segment-content>
<ion-segment-content id="free3">Free</ion-segment-content>
<ion-segment-content id="top3">Top</ion-segment-content>
</ion-segment-view>
<h2>
Swipe Gesture: Segment <ion-text color="danger">Disabled</ion-text>; Segment View
<ion-text color="danger">Disabled</ion-text>
</h2>
<ion-segment swipe-gesture="false" value="free4">
<ion-segment-button content-id="paid4" value="paid4">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button content-id="free4" value="free4">
<ion-label>Free</ion-label>
</ion-segment-button>
<ion-segment-button content-id="top4" value="top4">
<ion-label>Top</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view swipe-gesture="false">
<ion-segment-content id="paid4">Paid</ion-segment-content>
<ion-segment-content id="free4">Free</ion-segment-content>
<ion-segment-content id="top4">Top</ion-segment-content>
</ion-segment-view>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,51 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not vary across modes/directions
*/
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('segment-view: swipe gesture'), () => {
test('should allow swiping the segment view by default', async ({ page }) => {
await page.setContent(
`
<ion-segment-view>
<ion-segment-content id="paid">Paid</ion-segment-content>
<ion-segment-content id="free">Free</ion-segment-content>
<ion-segment-content id="top">Top</ion-segment-content>
</ion-segment-view>
`,
config
);
const segmentView = page.locator('ion-segment-view');
const allowsSwipe = await segmentView.evaluate((el: HTMLElement) => {
const style = getComputedStyle(el);
return style.overflowX !== 'hidden' && style.touchAction !== 'none';
});
expect(allowsSwipe).toBe(true);
});
test('should not allow swiping the segment view when swipeGesture is false', async ({ page }) => {
await page.setContent(
`
<ion-segment-view swipe-gesture="false">
<ion-segment-content id="paid">Paid</ion-segment-content>
<ion-segment-content id="free">Free</ion-segment-content>
<ion-segment-content id="top">Top</ion-segment-content>
</ion-segment-view>
`,
config
);
const segmentView = page.locator('ion-segment-view');
const allowsSwipe = await segmentView.evaluate((el: HTMLElement) => {
const style = getComputedStyle(el);
return style.overflowX !== 'hidden' && style.touchAction !== 'none';
});
expect(allowsSwipe).toBe(false);
});
});
});

View File

@@ -2113,14 +2113,14 @@ export declare interface IonSegmentContent extends Components.IonSegmentContent
@ProxyCmp({ @ProxyCmp({
inputs: ['disabled'] inputs: ['disabled', 'swipeGesture']
}) })
@Component({ @Component({
selector: 'ion-segment-view', selector: 'ion-segment-view',
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: ['disabled'], inputs: ['disabled', 'swipeGesture'],
}) })
export class IonSegmentView { export class IonSegmentView {
protected el: HTMLIonSegmentViewElement; protected el: HTMLIonSegmentViewElement;

View File

@@ -1889,14 +1889,14 @@ export declare interface IonSegmentContent extends Components.IonSegmentContent
@ProxyCmp({ @ProxyCmp({
defineCustomElementFn: defineIonSegmentView, defineCustomElementFn: defineIonSegmentView,
inputs: ['disabled'] inputs: ['disabled', 'swipeGesture']
}) })
@Component({ @Component({
selector: 'ion-segment-view', selector: 'ion-segment-view',
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: ['disabled'], inputs: ['disabled', 'swipeGesture'],
standalone: true standalone: true
}) })
export class IonSegmentView { export class IonSegmentView {

View File

@@ -899,6 +899,7 @@ export const IonSegmentContent: StencilVueComponent<JSX.IonSegmentContent> = /*@
export const IonSegmentView: StencilVueComponent<JSX.IonSegmentView> = /*@__PURE__*/ defineContainer<JSX.IonSegmentView>('ion-segment-view', defineIonSegmentView, [ export const IonSegmentView: StencilVueComponent<JSX.IonSegmentView> = /*@__PURE__*/ defineContainer<JSX.IonSegmentView>('ion-segment-view', defineIonSegmentView, [
'disabled', 'disabled',
'swipeGesture',
'ionSegmentViewScroll' 'ionSegmentViewScroll'
], [ ], [
'ionSegmentViewScroll' 'ionSegmentViewScroll'