feat(segment-view): adds support for new ion-segment-view component (#29969)

Issue number: resolves internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

Segments can only be changed by clicking a segment button, or dragging
the indicator

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

The segment/segment buttons can now be linked to segment content within
a segment view component. This content is scrollable/swipeable. Changing
the content will update the segment/indicator and vice-versa.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!--
  If this introduces a breaking change:
1. Describe the impact and migration path for existing applications
below.
  2. Update the BREAKING.md file with the breaking change.
3. Add "BREAKING CHANGE: [...]" to the commit description when merging.
See
https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer
for more information.
-->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

**Limitations:**
- Segment buttons **cannot** be disabled when connected ton
`ion-segment-content` instances
- The `ion-segment` **cannot** be without a value when linked with an
`ion-segment-view`. If no value is provided, the value will default to
the value of the first `ion-segment-content`


[Preview](https://ionic-framework-jlt8by2io-ionic1.vercel.app/src/components/segment-view/test/basic)
[Preview (disabled
state)](https://ionic-framework-jlt8by2io-ionic1.vercel.app/src/components/segment-view/test/disabled)

---------

Co-authored-by: Brandy Carney <brandyscarney@gmail.com>
This commit is contained in:
Tanner Reits
2024-10-31 12:20:02 -04:00
committed by Tanner Reits
parent 3628ea875a
commit 89508fb891
26 changed files with 1096 additions and 23 deletions

View File

@ -1542,6 +1542,7 @@ ion-segment,css-prop,--background,ios
ion-segment,css-prop,--background,md
ion-segment-button,shadow
ion-segment-button,prop,contentId,string | undefined,undefined,false,true
ion-segment-button,prop,disabled,boolean,false,false,false
ion-segment-button,prop,layout,"icon-bottom" | "icon-end" | "icon-hide" | "icon-start" | "icon-top" | "label-hide" | undefined,'icon-top',false,false
ion-segment-button,prop,mode,"ios" | "md",undefined,false,false
@ -1607,6 +1608,12 @@ ion-segment-button,part,indicator
ion-segment-button,part,indicator-background
ion-segment-button,part,native
ion-segment-content,shadow
ion-segment-view,shadow
ion-segment-view,prop,disabled,boolean,false,false,false
ion-segment-view,event,ionSegmentViewScroll,SegmentViewScrollEvent,true
ion-select,shadow
ion-select,prop,cancelText,string,'Cancel',false,false
ion-select,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true

View File

@ -34,6 +34,7 @@ import { NavigationHookCallback } from "./components/route/route-interface";
import { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface";
import { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface";
import { SegmentButtonLayout } from "./components/segment-button/segment-button-interface";
import { SegmentViewScrollEvent } from "./components/segment-view/segment-view-interface";
import { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface";
import { SelectModalOption } from "./components/select-modal/select-modal-interface";
import { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
@ -70,6 +71,7 @@ export { NavigationHookCallback } from "./components/route/route-interface";
export { SearchbarChangeEventDetail, SearchbarInputEventDetail } from "./components/searchbar/searchbar-interface";
export { SegmentChangeEventDetail, SegmentValue } from "./components/segment/segment-interface";
export { SegmentButtonLayout } from "./components/segment-button/segment-button-interface";
export { SegmentViewScrollEvent } from "./components/segment-view/segment-view-interface";
export { SelectChangeEventDetail, SelectCompareFn, SelectInterface } from "./components/select/select-interface";
export { SelectModalOption } from "./components/select-modal/select-modal-interface";
export { SelectPopoverOption } from "./components/select-popover/select-popover-interface";
@ -2696,6 +2698,10 @@ export namespace Components {
"value"?: SegmentValue;
}
interface IonSegmentButton {
/**
* The `id` of the segment content.
*/
"contentId"?: string;
/**
* If `true`, the user cannot interact with the segment button.
*/
@ -2718,6 +2724,19 @@ export namespace Components {
*/
"value": SegmentValue;
}
interface IonSegmentContent {
}
interface IonSegmentView {
/**
* If `true`, the segment view cannot be interacted with.
*/
"disabled": boolean;
/**
* @param id : The id of the segment content to display.
* @param smoothScroll : Whether to animate the scroll transition.
*/
"setContent": (id: string, smoothScroll?: boolean) => Promise<void>;
}
interface IonSelect {
/**
* The text to display on the cancel button.
@ -3424,6 +3443,10 @@ export interface IonSegmentCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonSegmentElement;
}
export interface IonSegmentViewCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonSegmentViewElement;
}
export interface IonSelectCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIonSelectElement;
@ -4420,6 +4443,29 @@ declare global {
prototype: HTMLIonSegmentButtonElement;
new (): HTMLIonSegmentButtonElement;
};
interface HTMLIonSegmentContentElement extends Components.IonSegmentContent, HTMLStencilElement {
}
var HTMLIonSegmentContentElement: {
prototype: HTMLIonSegmentContentElement;
new (): HTMLIonSegmentContentElement;
};
interface HTMLIonSegmentViewElementEventMap {
"ionSegmentViewScroll": SegmentViewScrollEvent;
}
interface HTMLIonSegmentViewElement extends Components.IonSegmentView, HTMLStencilElement {
addEventListener<K extends keyof HTMLIonSegmentViewElementEventMap>(type: K, listener: (this: HTMLIonSegmentViewElement, ev: IonSegmentViewCustomEvent<HTMLIonSegmentViewElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
removeEventListener<K extends keyof HTMLIonSegmentViewElementEventMap>(type: K, listener: (this: HTMLIonSegmentViewElement, ev: IonSegmentViewCustomEvent<HTMLIonSegmentViewElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}
var HTMLIonSegmentViewElement: {
prototype: HTMLIonSegmentViewElement;
new (): HTMLIonSegmentViewElement;
};
interface HTMLIonSelectElementEventMap {
"ionChange": SelectChangeEventDetail;
"ionCancel": void;
@ -4735,6 +4781,8 @@ declare global {
"ion-searchbar": HTMLIonSearchbarElement;
"ion-segment": HTMLIonSegmentElement;
"ion-segment-button": HTMLIonSegmentButtonElement;
"ion-segment-content": HTMLIonSegmentContentElement;
"ion-segment-view": HTMLIonSegmentViewElement;
"ion-select": HTMLIonSelectElement;
"ion-select-modal": HTMLIonSelectModalElement;
"ion-select-option": HTMLIonSelectOptionElement;
@ -7465,6 +7513,10 @@ declare namespace LocalJSX {
"value"?: SegmentValue;
}
interface IonSegmentButton {
/**
* The `id` of the segment content.
*/
"contentId"?: string;
/**
* If `true`, the user cannot interact with the segment button.
*/
@ -7486,6 +7538,18 @@ declare namespace LocalJSX {
*/
"value"?: SegmentValue;
}
interface IonSegmentContent {
}
interface IonSegmentView {
/**
* If `true`, the segment view cannot be interacted with.
*/
"disabled"?: boolean;
/**
* Emitted when the segment view is scrolled.
*/
"onIonSegmentViewScroll"?: (event: IonSegmentViewCustomEvent<SegmentViewScrollEvent>) => void;
}
interface IonSelect {
/**
* The text to display on the cancel button.
@ -8182,6 +8246,8 @@ declare namespace LocalJSX {
"ion-searchbar": IonSearchbar;
"ion-segment": IonSegment;
"ion-segment-button": IonSegmentButton;
"ion-segment-content": IonSegmentContent;
"ion-segment-view": IonSegmentView;
"ion-select": IonSelect;
"ion-select-modal": IonSelectModal;
"ion-select-option": IonSelectOption;
@ -8282,6 +8348,8 @@ declare module "@stencil/core" {
"ion-searchbar": LocalJSX.IonSearchbar & JSXBase.HTMLAttributes<HTMLIonSearchbarElement>;
"ion-segment": LocalJSX.IonSegment & JSXBase.HTMLAttributes<HTMLIonSegmentElement>;
"ion-segment-button": LocalJSX.IonSegmentButton & JSXBase.HTMLAttributes<HTMLIonSegmentButtonElement>;
"ion-segment-content": LocalJSX.IonSegmentContent & JSXBase.HTMLAttributes<HTMLIonSegmentContentElement>;
"ion-segment-view": LocalJSX.IonSegmentView & JSXBase.HTMLAttributes<HTMLIonSegmentViewElement>;
"ion-select": LocalJSX.IonSelect & JSXBase.HTMLAttributes<HTMLIonSelectElement>;
"ion-select-modal": LocalJSX.IonSelectModal & JSXBase.HTMLAttributes<HTMLIonSelectModalElement>;
"ion-select-option": LocalJSX.IonSelectOption & JSXBase.HTMLAttributes<HTMLIonSelectOptionElement>;

View File

@ -36,6 +36,11 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
@State() checked = false;
/**
* The `id` of the segment content.
*/
@Prop({ reflect: true }) contentId?: string;
/**
* If `true`, the user cannot interact with the segment button.
*/
@ -67,6 +72,30 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
addEventListener(segmentEl, 'ionSelect', this.updateState);
addEventListener(segmentEl, 'ionStyle', this.updateStyle);
}
// Return if there is no contentId defined
if (!this.contentId) return;
// Attempt to find the Segment Content by its contentId
const segmentContent = document.getElementById(this.contentId) as HTMLIonSegmentContentElement | null;
// If no associated Segment Content exists, log an error and return
if (!segmentContent) {
console.error(`Segment Button: Unable to find Segment Content with id="${this.contentId}".`);
return;
}
// Ensure the found element is a valid ION-SEGMENT-CONTENT
if (segmentContent.tagName !== 'ION-SEGMENT-CONTENT') {
console.error(`Segment Button: Element with id="${this.contentId}" is not an <ion-segment-content> element.`);
return;
}
// Prevent buttons from being disabled when associated with segment content
if (this.disabled) {
console.warn(`Segment Button: Segment buttons cannot be disabled when associated with an <ion-segment-content>.`);
this.disabled = false;
}
}
disconnectedCallback() {
@ -161,13 +190,7 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
</span>
{mode === 'md' && <ion-ripple-effect></ion-ripple-effect>}
</button>
<div
part="indicator"
class={{
'segment-button-indicator': true,
'segment-button-indicator-animated': true,
}}
>
<div part="indicator" class="segment-button-indicator segment-button-indicator-animated">
<div part="indicator-background" class="segment-button-indicator-background"></div>
</div>
</Host>

View File

@ -0,0 +1,11 @@
// Segment Content
// --------------------------------------------------
:host {
scroll-snap-align: center;
scroll-snap-stop: always;
flex-shrink: 0;
width: 100%;
}

View File

@ -0,0 +1,17 @@
import type { ComponentInterface } from '@stencil/core';
import { Component, Host, h } from '@stencil/core';
@Component({
tag: 'ion-segment-content',
styleUrl: 'segment-content.scss',
shadow: true,
})
export class SegmentContent implements ComponentInterface {
render() {
return (
<Host>
<slot></slot>
</Host>
);
}
}

View File

@ -0,0 +1,4 @@
export interface SegmentViewScrollEvent {
scrollRatio: number;
isManualScroll: boolean;
}

View File

@ -0,0 +1,9 @@
@import "./segment-view";
@import "../segment-button/segment-button.ios.vars";
// iOS Segment View
// --------------------------------------------------
:host(.segment-view-disabled) {
opacity: $segment-button-ios-opacity-disabled;
}

View File

@ -0,0 +1,9 @@
@import "./segment-view";
@import "../segment-button/segment-button.md.vars";
// Material Design Segment View
// --------------------------------------------------
:host(.segment-view-disabled) {
opacity: $segment-button-md-opacity-disabled;
}

View File

@ -0,0 +1,31 @@
// Segment View
// --------------------------------------------------
:host {
display: flex;
height: 100%;
overflow-x: scroll;
scroll-snap-type: x mandatory;
/* Hide scrollbar in Firefox */
scrollbar-width: none;
/* Hide scrollbar in IE and Edge */
-ms-overflow-style: none;
}
/* Hide scrollbar in webkit */
:host::-webkit-scrollbar {
display: none;
}
:host(.segment-view-disabled) {
touch-action: none;
overflow-x: hidden;
}
:host(.segment-view-scroll-disabled) {
pointer-events: none;
}

View File

@ -0,0 +1,153 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Listen, Method, Prop, State, h } from '@stencil/core';
import type { SegmentViewScrollEvent } from './segment-view-interface';
@Component({
tag: 'ion-segment-view',
styleUrls: {
ios: 'segment-view.ios.scss',
md: 'segment-view.md.scss',
},
shadow: true,
})
export class SegmentView implements ComponentInterface {
private scrollEndTimeout: ReturnType<typeof setTimeout> | null = null;
private isTouching = false;
@Element() el!: HTMLElement;
/**
* If `true`, the segment view cannot be interacted with.
*/
@Prop() disabled = false;
/**
* @internal
*
* If `true`, the segment view is scrollable.
* If `false`, pointer events will be disabled. This is to prevent issues with
* quickly scrolling after interacting with a segment button.
*/
@State() isManualScroll?: boolean;
/**
* Emitted when the segment view is scrolled.
*/
@Event() ionSegmentViewScroll!: EventEmitter<SegmentViewScrollEvent>;
@Listen('scroll')
handleScroll(ev: Event) {
const { scrollLeft, scrollWidth, clientWidth } = ev.target as HTMLElement;
const scrollRatio = scrollLeft / (scrollWidth - clientWidth);
this.ionSegmentViewScroll.emit({
scrollRatio,
isManualScroll: this.isManualScroll ?? true,
});
// Reset the timeout to check for scroll end
this.resetScrollEndTimeout();
}
/**
* Handle touch start event to know when the user is actively dragging the segment view.
*/
@Listen('touchstart')
handleScrollStart() {
if (this.scrollEndTimeout) {
clearTimeout(this.scrollEndTimeout);
this.scrollEndTimeout = null;
}
this.isTouching = true;
}
/**
* Handle touch end event to know when the user is no longer dragging the segment view.
*/
@Listen('touchend')
handleTouchEnd() {
this.isTouching = false;
}
/**
* Reset the scroll end detection timer. This is called on every scroll event.
*/
private resetScrollEndTimeout() {
if (this.scrollEndTimeout) {
clearTimeout(this.scrollEndTimeout);
this.scrollEndTimeout = null;
}
this.scrollEndTimeout = setTimeout(
() => {
this.checkForScrollEnd();
},
// Setting this to a lower value may result in inconsistencies in behavior
// across browsers (particularly Firefox).
// Ideally, all of this logic is removed once the scroll end event is
// supported on all browsers (https://caniuse.com/?search=scrollend)
100
);
}
/**
* Check if the scroll has ended and the user is not actively touching.
* If the conditions are met (active content is enabled and no active touch),
* reset the scroll position and emit the scroll end event.
*/
private checkForScrollEnd() {
// Only emit scroll end event if the active content is not disabled and
// the user is not touching the segment view
if (!this.isTouching) {
this.isManualScroll = undefined;
}
}
/**
* @internal
*
* This method is used to programmatically set the displayed segment content
* in the segment view. Calling this method will update the `value` of the
* corresponding segment button.
*
* @param id: The id of the segment content to display.
* @param smoothScroll: Whether to animate the scroll transition.
*/
@Method()
async setContent(id: string, smoothScroll = true) {
const contents = this.getSegmentContents();
const index = contents.findIndex((content) => content.id === id);
if (index === -1) return;
this.isManualScroll = false;
this.resetScrollEndTimeout();
const contentWidth = this.el.offsetWidth;
this.el.scrollTo({
top: 0,
left: index * contentWidth,
behavior: smoothScroll ? 'smooth' : 'instant',
});
}
private getSegmentContents(): HTMLIonSegmentContentElement[] {
return Array.from(this.el.querySelectorAll('ion-segment-content'));
}
render() {
const { disabled, isManualScroll } = this;
return (
<Host
class={{
'segment-view-disabled': disabled,
'segment-view-scroll-disabled': isManualScroll === false,
}}
>
<slot></slot>
</Host>
);
}
}

View File

@ -0,0 +1,164 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Segment View - Basic</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>
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 - Basic</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-segment id="noValueSegment">
<ion-segment-button content-id="no" value="no">
<ion-label>No</ion-label>
</ion-segment-button>
<ion-segment-button content-id="value" value="value">
<ion-label>Value</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view id="noValueSegmentView">
<ion-segment-content id="no">No</ion-segment-content>
<ion-segment-content id="value">Value</ion-segment-content>
</ion-segment-view>
<ion-segment value="free">
<ion-segment-button content-id="paid" value="paid">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button style="min-width: 200px" 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>
<ion-segment value="peach" scrollable>
<ion-segment-button content-id="orange" value="orange">
<ion-label>Orange</ion-label>
</ion-segment-button>
<ion-segment-button content-id="banana" value="banana">
<ion-label>Banana</ion-label>
</ion-segment-button>
<ion-segment-button content-id="pear" value="pear">
<ion-label>Pear</ion-label>
</ion-segment-button>
<ion-segment-button content-id="peach" value="peach">
<ion-label>Peach</ion-label>
</ion-segment-button>
<ion-segment-button content-id="grape" value="grape">
<ion-label>Grape</ion-label>
</ion-segment-button>
<ion-segment-button content-id="mango" value="mango">
<ion-label>Mango</ion-label>
</ion-segment-button>
<ion-segment-button content-id="apple" value="apple">
<ion-label>Apple</ion-label>
</ion-segment-button>
<ion-segment-button content-id="strawberry" value="strawberry">
<ion-label>Strawberry</ion-label>
</ion-segment-button>
<ion-segment-button content-id="cherry" value="cherry">
<ion-label>Cherry</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="orange">Orange</ion-segment-content>
<ion-segment-content id="banana">Banana</ion-segment-content>
<ion-segment-content id="pear">Pear</ion-segment-content>
<ion-segment-content id="peach">Peach</ion-segment-content>
<ion-segment-content id="grape">Grape</ion-segment-content>
<ion-segment-content id="mango">Mango</ion-segment-content>
<ion-segment-content id="apple">Apple</ion-segment-content>
<ion-segment-content id="strawberry">Strawberry</ion-segment-content>
<ion-segment-content id="cherry">Cherry</ion-segment-content>
</ion-segment-view>
<button class="expand" onClick="changeSegmentContent()">Change Segment Content</button>
<button class="expand" onClick="clearSegmentValue()">Clear Segment Value</button>
</ion-content>
<ion-footer>
<ion-toolbar>
<ion-title>Footer</ion-title>
</ion-toolbar>
</ion-footer>
<script>
function changeSegmentContent() {
const segment = document.querySelector('#noValueSegment');
const segmentView = document.querySelector('#noValueSegmentView');
let currentValue = segment.value;
if (currentValue === 'value') {
currentValue = 'no';
} else {
currentValue = 'value';
}
segment.value = currentValue;
}
async function clearSegmentValue() {
const segmentView = document.querySelector('#noValueSegmentView');
segmentView.setContent('no', false);
// Set timeout to ensure the value is cleared after
// the segment content is updated
setTimeout(() => {
const segment = document.querySelector('#noValueSegment');
segment.value = undefined;
});
}
</script>
</ion-app>
</body>
</html>

View File

@ -0,0 +1,173 @@
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: basic'), () => {
test('should show the first content with no initial value', async ({ page }) => {
await page.setContent(
`
<ion-segment>
<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>
`,
config
);
const segmentContent = page.locator('ion-segment-content[id="paid"]');
await expect(segmentContent).toBeInViewport();
});
test('should show the content matching the initial value', async ({ page }) => {
await page.setContent(
`
<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>
`,
config
);
const segmentContent = page.locator('ion-segment-content[id="free"]');
await expect(segmentContent).toBeInViewport();
});
test('should update the content when changing the value by clicking a segment button', async ({ page }) => {
await page.setContent(
`
<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>
`,
config
);
await page.locator('ion-segment-button[value="top"]').click();
const segmentContent = page.locator('ion-segment-content[id="top"]');
await expect(segmentContent).toBeInViewport();
});
});
test('should set correct segment button as checked when changing the value by scrolling the segment content', async ({
page,
}) => {
await page.setContent(
`
<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>
`,
config
);
await page
.locator('ion-segment-view')
.evaluate(
(segmentView: HTMLIonSegmentViewElement) => !segmentView.classList.contains('segment-view-scroll-disabled')
);
await page.waitForChanges();
await page.locator('ion-segment-content[id="top"]').scrollIntoViewIfNeeded();
const segmentButton = page.locator('ion-segment-button[value="top"]');
await expect(segmentButton).toHaveClass(/segment-button-checked/);
});
test('should set correct segment button as checked and show correct content when programmatically setting the segment vale', async ({
page,
}) => {
await page.setContent(
`
<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>
`,
config
);
await page
.locator('ion-segment-view')
.evaluate(
(segmentView: HTMLIonSegmentViewElement) => !segmentView.classList.contains('segment-view-scroll-disabled')
);
await page.waitForChanges();
await page.locator('ion-segment').evaluate((segment: HTMLIonSegmentElement) => (segment.value = 'top'));
const segmentButton = page.locator('ion-segment-button[value="top"]');
await expect(segmentButton).toHaveClass(/segment-button-checked/);
const segmentContent = page.locator('ion-segment-content[id="top"]');
await expect(segmentContent).toBeInViewport();
});
});

View File

@ -0,0 +1,103 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Segment View - Disabled</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>
ion-segment-view {
height: 100px;
}
ion-segment-content {
display: flex;
justify-content: center;
align-items: center;
}
ion-segment-content:nth-of-type(1) {
background: lightpink;
}
ion-segment-content:nth-of-type(2) {
background: lightblue;
}
ion-segment-content:nth-of-type(3) {
background: lightgreen;
}
ion-segment-content:nth-of-type(4) {
background: lightgoldenrodyellow;
}
</style>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Segment View - Disabled</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-segment>
<ion-segment-button disabled content-id="all" value="all">
<ion-label>All</ion-label>
</ion-segment-button>
<ion-segment-button content-id="favorites" value="favorites">
<ion-label>Favorites</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view>
<ion-segment-content id="all">All</ion-segment-content>
<ion-segment-content id="favorites">Favorites</ion-segment-content>
</ion-segment-view>
<ion-segment disabled value="paid">
<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>
<ion-segment value="reading-list">
<ion-segment-button content-id="bookmarks" value="bookmarks">
<ion-label>Bookmarks</ion-label>
</ion-segment-button>
<ion-segment-button content-id="reading-list" value="reading-list">
<ion-label>Reading List</ion-label>
</ion-segment-button>
<ion-segment-button content-id="shared-links" value="shared-links">
<ion-label>Shared Links</ion-label>
</ion-segment-button>
</ion-segment>
<ion-segment-view disabled>
<ion-segment-content id="bookmarks">Bookmarks</ion-segment-content>
<ion-segment-content id="reading-list">Reading List</ion-segment-content>
<ion-segment-content id="shared-links">Shared Links</ion-segment-content>
</ion-segment-view>
</ion-content>
</ion-app>
</body>
</html>

View File

@ -0,0 +1,49 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not vary across directions
*/
configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('segment-view: disabled'), () => {
test('should not have visual regressions', async ({ page }) => {
await page.goto('/src/components/segment-view/test/disabled', config);
await expect(page).toHaveScreenshot(screenshot(`segment-view-disabled`));
});
});
});
/**
* This behavior does not vary across modes/directions
*/
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('segment-view: disabled'), () => {
test('should keep button enabled even when disabled prop is set', async ({ page }) => {
await page.setContent(
`
<ion-segment>
<ion-segment-button content-id="paid" value="paid">
<ion-label>Paid</ion-label>
</ion-segment-button>
<ion-segment-button disabled 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 disabled 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 segmentButton = page.locator('ion-segment-button[value="free"]');
await expect(segmentButton).not.toHaveClass(/segment-button-disabled/);
});
});
});

View File

@ -7,6 +7,7 @@ import { createColorClasses, hostContext } from '@utils/theme';
import { getIonMode } from '../../global/ionic-global';
import type { Color, StyleEventDetail } from '../../interface';
import type { SegmentViewScrollEvent } from '../segment-view/segment-view-interface';
import type { SegmentChangeEventDetail, SegmentValue } from './segment-interface';
@ -27,6 +28,16 @@ export class Segment implements ComponentInterface {
// Value before the segment is dragged
private valueBeforeGesture?: SegmentValue;
private segmentViewEl?: HTMLIonSegmentViewElement | null = null;
private lastNextIndex?: number;
/**
* Whether to update the segment view, if exists, when the value changes.
* This behavior is enabled by default, but is set false when scrolling content views
* since we don't want to "double scroll" the segment view.
*/
private triggerScrollOnValueChange?: boolean;
@Element() el!: HTMLIonSegmentElement;
@State() activated = false;
@ -78,15 +89,43 @@ export class Segment implements ComponentInterface {
@Prop({ mutable: true }) value?: SegmentValue;
@Watch('value')
protected valueChanged(value: SegmentValue | undefined) {
protected valueChanged(value: SegmentValue | undefined, oldValue?: SegmentValue | undefined) {
// Force a value to exist if we're using a segment view
if (this.segmentViewEl && value === undefined) {
this.value = this.getButtons()[0].value;
return;
}
if (oldValue !== undefined && value !== undefined) {
const buttons = this.getButtons();
const previous = buttons.find((button) => button.value === oldValue);
const current = buttons.find((button) => button.value === value);
if (previous && current) {
if (!this.segmentViewEl) {
this.checkButton(previous, current);
} else if (this.triggerScrollOnValueChange !== false) {
this.updateSegmentView();
}
}
} else if (value !== undefined && oldValue === undefined && this.segmentViewEl) {
this.updateSegmentView();
}
/**
* `ionSelect` is emitted every time the value changes (internal or external changes).
* Used by `ion-segment-button` to determine if the button should be checked.
*/
this.ionSelect.emit({ value });
// The scroll listener should handle scrolling the active button into view as needed
if (!this.segmentViewEl) {
this.scrollActiveButtonIntoView();
}
this.triggerScrollOnValueChange = undefined;
}
/**
* If `true`, navigating to an `ion-segment-button` with the keyboard will focus and select the element.
* If `false`, keyboard navigation will only focus the `ion-segment-button` element.
@ -118,10 +157,14 @@ export class Segment implements ComponentInterface {
disabledChanged() {
this.gestureChanged();
if (!this.segmentViewEl) {
const buttons = this.getButtons();
for (const button of buttons) {
button.disabled = this.disabled;
}
} else {
this.segmentViewEl.disabled = this.disabled;
}
}
private gestureChanged() {
@ -132,6 +175,12 @@ export class Segment implements ComponentInterface {
connectedCallback() {
this.emitStyle();
this.segmentViewEl = this.getSegmentView();
}
disconnectedCallback() {
this.segmentViewEl = null;
}
componentWillLoad() {
@ -170,6 +219,10 @@ export class Segment implements ComponentInterface {
if (this.disabled) {
this.disabledChanged();
}
// Update segment view based on the initial value,
// but do not animate the scroll
this.updateSegmentView(false);
}
onStart(detail: GestureDetail) {
@ -192,6 +245,7 @@ export class Segment implements ComponentInterface {
if (value !== undefined) {
if (this.valueBeforeGesture !== value) {
this.emitValueChange();
this.updateSegmentView();
}
}
this.valueBeforeGesture = undefined;
@ -208,7 +262,7 @@ export class Segment implements ComponentInterface {
this.ionChange.emit({ value });
}
private getButtons() {
private getButtons(): HTMLIonSegmentButtonElement[] {
return Array.from(this.el.querySelectorAll('ion-segment-button'));
}
@ -224,11 +278,7 @@ export class Segment implements ComponentInterface {
const buttons = this.getButtons();
buttons.forEach((button) => {
if (activated) {
button.classList.add('segment-button-activated');
} else {
button.classList.remove('segment-button-activated');
}
button.classList.toggle('segment-button-activated', activated);
});
this.activated = activated;
}
@ -293,6 +343,8 @@ export class Segment implements ComponentInterface {
// Remove the transform to slide the indicator back to the button clicked
currentIndicator.style.setProperty('transform', '');
this.scrollActiveButtonIntoView(true);
});
this.value = current.value;
@ -312,6 +364,74 @@ export class Segment implements ComponentInterface {
}
}
private getSegmentView() {
const buttons = this.getButtons();
// Get the first button with a contentId
const firstContentId = buttons.find((button: HTMLIonSegmentButtonElement) => button.contentId);
// Get the segment content with an id matching the button's contentId
const segmentContent = document.querySelector(`ion-segment-content[id="${firstContentId?.contentId}"]`);
// Return the segment view for that matching segment content
return segmentContent?.closest('ion-segment-view');
}
@Listen('ionSegmentViewScroll', { target: 'body' })
handleSegmentViewScroll(ev: CustomEvent<SegmentViewScrollEvent>) {
const { scrollRatio, isManualScroll } = ev.detail;
if (!isManualScroll) {
return;
}
const dispatchedFrom = ev.target as HTMLElement;
const segmentViewEl = this.segmentViewEl as EventTarget;
const segmentEl = this.el;
// Only update the indicator if the event was dispatched from the correct segment view
if (ev.composedPath().includes(segmentViewEl) || dispatchedFrom?.contains(segmentEl)) {
const buttons = this.getButtons();
// If no buttons are found or there is no value set then do nothing
if (!buttons.length) return;
const index = buttons.findIndex((button) => button.value === this.value);
const current = buttons[index];
const nextIndex = Math.round(scrollRatio * (buttons.length - 1));
if (this.lastNextIndex === undefined || this.lastNextIndex !== nextIndex) {
this.lastNextIndex = nextIndex;
this.triggerScrollOnValueChange = false;
this.checkButton(current, buttons[nextIndex]);
this.emitValueChange();
}
}
}
/**
* Finds the related segment view and sets its current content
* based on the selected segment button. This method
* should be called on initial load of the segment,
* after the gesture is completed (if dragging between segments)
* and when a segment button is clicked directly.
*/
private updateSegmentView(smoothScroll = true) {
const buttons = this.getButtons();
const button = buttons.find((btn) => btn.value === this.value);
// If the button does not have a contentId then there is
// no associated segment view to update
if (!button?.contentId) {
return;
}
const segmentView = this.segmentViewEl;
if (segmentView) {
segmentView.setContent(button.contentId, smoothScroll);
}
}
private scrollActiveButtonIntoView(smoothScroll = true) {
const { scrollable, value, el } = this;
@ -492,7 +612,13 @@ export class Segment implements ComponentInterface {
this.emitValueChange();
}
if (this.scrollable || !this.swipeGesture) {
if (this.segmentViewEl) {
this.updateSegmentView();
if (this.scrollable && previous) {
this.checkButton(previous, current);
}
} else if (this.scrollable || !this.swipeGesture) {
if (previous) {
this.checkButton(previous, current);
} else {

View File

@ -69,6 +69,8 @@ export const DIRECTIVES = [
d.IonSearchbar,
d.IonSegment,
d.IonSegmentButton,
d.IonSegmentContent,
d.IonSegmentView,
d.IonSelect,
d.IonSelectModal,
d.IonSelectOption,

View File

@ -1987,14 +1987,14 @@ This event will not emit when programmatically setting the `value` property.
@ProxyCmp({
inputs: ['disabled', 'layout', 'mode', 'type', 'value']
inputs: ['contentId', 'disabled', 'layout', 'mode', 'type', 'value']
})
@Component({
selector: 'ion-segment-button',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['disabled', 'layout', 'mode', 'type', 'value'],
inputs: ['contentId', 'disabled', 'layout', 'mode', 'type', 'value'],
})
export class IonSegmentButton {
protected el: HTMLElement;
@ -2008,6 +2008,57 @@ export class IonSegmentButton {
export declare interface IonSegmentButton extends Components.IonSegmentButton {}
@ProxyCmp({
})
@Component({
selector: 'ion-segment-content',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: [],
})
export class IonSegmentContent {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
}
}
export declare interface IonSegmentContent extends Components.IonSegmentContent {}
@ProxyCmp({
inputs: ['disabled']
})
@Component({
selector: 'ion-segment-view',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['disabled'],
})
export class IonSegmentView {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
proxyOutputs(this, this.el, ['ionSegmentViewScroll']);
}
}
import type { SegmentViewScrollEvent as IIonSegmentViewSegmentViewScrollEvent } from '@ionic/core';
export declare interface IonSegmentView extends Components.IonSegmentView {
/**
* Emitted when the segment view is scrolled.
*/
ionSegmentViewScroll: EventEmitter<CustomEvent<IIonSegmentViewSegmentViewScrollEvent>>;
}
@ProxyCmp({
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'expandedIcon', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'shape', 'toggleIcon', 'value'],
methods: ['open']

View File

@ -65,6 +65,8 @@ import { defineCustomElement as defineIonReorderGroup } from '@ionic/core/compon
import { defineCustomElement as defineIonRippleEffect } from '@ionic/core/components/ion-ripple-effect.js';
import { defineCustomElement as defineIonRow } from '@ionic/core/components/ion-row.js';
import { defineCustomElement as defineIonSegmentButton } from '@ionic/core/components/ion-segment-button.js';
import { defineCustomElement as defineIonSegmentContent } from '@ionic/core/components/ion-segment-content.js';
import { defineCustomElement as defineIonSegmentView } from '@ionic/core/components/ion-segment-view.js';
import { defineCustomElement as defineIonSelectModal } from '@ionic/core/components/ion-select-modal.js';
import { defineCustomElement as defineIonSelectOption } from '@ionic/core/components/ion-select-option.js';
import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js';
@ -1820,14 +1822,14 @@ export declare interface IonRow extends Components.IonRow {}
@ProxyCmp({
defineCustomElementFn: defineIonSegmentButton,
inputs: ['disabled', 'layout', 'mode', 'type', 'value']
inputs: ['contentId', 'disabled', 'layout', 'mode', 'type', 'value']
})
@Component({
selector: 'ion-segment-button',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['disabled', 'layout', 'mode', 'type', 'value'],
inputs: ['contentId', 'disabled', 'layout', 'mode', 'type', 'value'],
standalone: true
})
export class IonSegmentButton {
@ -1842,6 +1844,61 @@ export class IonSegmentButton {
export declare interface IonSegmentButton extends Components.IonSegmentButton {}
@ProxyCmp({
defineCustomElementFn: defineIonSegmentContent
})
@Component({
selector: 'ion-segment-content',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: [],
standalone: true
})
export class IonSegmentContent {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
}
}
export declare interface IonSegmentContent extends Components.IonSegmentContent {}
@ProxyCmp({
defineCustomElementFn: defineIonSegmentView,
inputs: ['disabled']
})
@Component({
selector: 'ion-segment-view',
changeDetection: ChangeDetectionStrategy.OnPush,
template: '<ng-content></ng-content>',
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
inputs: ['disabled'],
standalone: true
})
export class IonSegmentView {
protected el: HTMLElement;
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
c.detach();
this.el = r.nativeElement;
proxyOutputs(this, this.el, ['ionSegmentViewScroll']);
}
}
import type { SegmentViewScrollEvent as IIonSegmentViewSegmentViewScrollEvent } from '@ionic/core/components';
export declare interface IonSegmentView extends Components.IonSegmentView {
/**
* Emitted when the segment view is scrolled.
*/
ionSegmentViewScroll: EventEmitter<CustomEvent<IIonSegmentViewSegmentViewScrollEvent>>;
}
@ProxyCmp({
defineCustomElementFn: defineIonSelectModal,
inputs: ['header', 'multiple', 'options']

View File

@ -61,6 +61,8 @@ import { defineCustomElement as defineIonRow } from '@ionic/core/components/ion-
import { defineCustomElement as defineIonSearchbar } from '@ionic/core/components/ion-searchbar.js';
import { defineCustomElement as defineIonSegment } from '@ionic/core/components/ion-segment.js';
import { defineCustomElement as defineIonSegmentButton } from '@ionic/core/components/ion-segment-button.js';
import { defineCustomElement as defineIonSegmentContent } from '@ionic/core/components/ion-segment-content.js';
import { defineCustomElement as defineIonSegmentView } from '@ionic/core/components/ion-segment-view.js';
import { defineCustomElement as defineIonSelect } from '@ionic/core/components/ion-select.js';
import { defineCustomElement as defineIonSelectModal } from '@ionic/core/components/ion-select-modal.js';
import { defineCustomElement as defineIonSelectOption } from '@ionic/core/components/ion-select-option.js';
@ -131,6 +133,8 @@ export const IonRow = /*@__PURE__*/createReactComponent<JSX.IonRow, HTMLIonRowEl
export const IonSearchbar = /*@__PURE__*/createReactComponent<JSX.IonSearchbar, HTMLIonSearchbarElement>('ion-searchbar', undefined, undefined, defineIonSearchbar);
export const IonSegment = /*@__PURE__*/createReactComponent<JSX.IonSegment, HTMLIonSegmentElement>('ion-segment', undefined, undefined, defineIonSegment);
export const IonSegmentButton = /*@__PURE__*/createReactComponent<JSX.IonSegmentButton, HTMLIonSegmentButtonElement>('ion-segment-button', undefined, undefined, defineIonSegmentButton);
export const IonSegmentContent = /*@__PURE__*/createReactComponent<JSX.IonSegmentContent, HTMLIonSegmentContentElement>('ion-segment-content', undefined, undefined, defineIonSegmentContent);
export const IonSegmentView = /*@__PURE__*/createReactComponent<JSX.IonSegmentView, HTMLIonSegmentViewElement>('ion-segment-view', undefined, undefined, defineIonSegmentView);
export const IonSelect = /*@__PURE__*/createReactComponent<JSX.IonSelect, HTMLIonSelectElement>('ion-select', undefined, undefined, defineIonSelect);
export const IonSelectModal = /*@__PURE__*/createReactComponent<JSX.IonSelectModal, HTMLIonSelectModalElement>('ion-select-modal', undefined, undefined, defineIonSelectModal);
export const IonSelectOption = /*@__PURE__*/createReactComponent<JSX.IonSelectOption, HTMLIonSelectOptionElement>('ion-select-option', undefined, undefined, defineIonSelectOption);

View File

@ -67,6 +67,8 @@ import { defineCustomElement as defineIonRow } from '@ionic/core/components/ion-
import { defineCustomElement as defineIonSearchbar } from '@ionic/core/components/ion-searchbar.js';
import { defineCustomElement as defineIonSegment } from '@ionic/core/components/ion-segment.js';
import { defineCustomElement as defineIonSegmentButton } from '@ionic/core/components/ion-segment-button.js';
import { defineCustomElement as defineIonSegmentContent } from '@ionic/core/components/ion-segment-content.js';
import { defineCustomElement as defineIonSegmentView } from '@ionic/core/components/ion-segment-view.js';
import { defineCustomElement as defineIonSelect } from '@ionic/core/components/ion-select.js';
import { defineCustomElement as defineIonSelectModal } from '@ionic/core/components/ion-select-modal.js';
import { defineCustomElement as defineIonSelectOption } from '@ionic/core/components/ion-select-option.js';
@ -745,6 +747,7 @@ export const IonSegment = /*@__PURE__*/ defineContainer<JSX.IonSegment, JSX.IonS
export const IonSegmentButton = /*@__PURE__*/ defineContainer<JSX.IonSegmentButton, JSX.IonSegmentButton["value"]>('ion-segment-button', defineIonSegmentButton, [
'contentId',
'disabled',
'layout',
'type',
@ -753,6 +756,15 @@ export const IonSegmentButton = /*@__PURE__*/ defineContainer<JSX.IonSegmentButt
'value', 'ion-change');
export const IonSegmentContent = /*@__PURE__*/ defineContainer<JSX.IonSegmentContent>('ion-segment-content', defineIonSegmentContent);
export const IonSegmentView = /*@__PURE__*/ defineContainer<JSX.IonSegmentView>('ion-segment-view', defineIonSegmentView, [
'disabled',
'ionSegmentViewScroll'
]);
export const IonSelect = /*@__PURE__*/ defineContainer<JSX.IonSelect, JSX.IonSelect["value"]>('ion-select', defineIonSelect, [
'cancelText',
'color',