mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
58 Commits
ionic-modu
...
tr/segment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8b5658b8a | ||
|
|
d6f7cc86ab | ||
|
|
88b9af9a44 | ||
|
|
c4c89ae005 | ||
|
|
cf51b7127c | ||
|
|
8e50616d42 | ||
|
|
d804924f8f | ||
|
|
95c5d1b86c | ||
|
|
e26c8ff8ad | ||
|
|
7b67c26ba0 | ||
|
|
a75baa1f10 | ||
|
|
7da0beb27f | ||
|
|
f171202ac7 | ||
|
|
1bb7fe87f7 | ||
|
|
9823e0d2a9 | ||
|
|
26c2697830 | ||
|
|
ca3e43bc3b | ||
|
|
b8dd17eae7 | ||
|
|
475de8b6c7 | ||
|
|
b94bec20de | ||
|
|
279300fd3e | ||
|
|
59e306bd6d | ||
|
|
641dc0b82a | ||
|
|
e6547432e5 | ||
|
|
c6ec156dcb | ||
|
|
89d8e90ab7 | ||
|
|
ae2704fdc8 | ||
|
|
16c728b040 | ||
|
|
0fa5c99d99 | ||
|
|
0a13ab449a | ||
|
|
cbee05e488 | ||
|
|
bdc6933cf6 | ||
|
|
7fe1c094ec | ||
|
|
c7bae079c2 | ||
|
|
1d645c9f3f | ||
|
|
699ce9779f | ||
|
|
d811221750 | ||
|
|
798e725712 | ||
|
|
15b8b8fe68 | ||
|
|
522cbc6180 | ||
|
|
094d9a8553 | ||
|
|
b75650b2f7 | ||
|
|
f07a5b1039 | ||
|
|
5401e8dc24 | ||
|
|
faa7065a70 | ||
|
|
d8f27d8f7b | ||
|
|
4c0407ed52 | ||
|
|
9103c403b2 | ||
|
|
e6f76d53a9 | ||
|
|
686d943b65 | ||
|
|
ba285306d6 | ||
|
|
44e8374791 | ||
|
|
678990d77a | ||
|
|
44a0855844 | ||
|
|
00c378f0f5 | ||
|
|
8af4d74846 | ||
|
|
0324a78c2b | ||
|
|
fa74077820 |
11
core/api.txt
11
core/api.txt
@@ -1541,6 +1541,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
|
||||
@@ -1606,6 +1607,16 @@ ion-segment-button,part,indicator
|
||||
ion-segment-button,part,indicator-background
|
||||
ion-segment-button,part,native
|
||||
|
||||
ion-segment-content,shadow
|
||||
ion-segment-content,prop,disabled,boolean,false,false,false
|
||||
|
||||
ion-segment-view,shadow
|
||||
ion-segment-view,prop,disabled,boolean,false,false,false
|
||||
ion-segment-view,method,setContent,setContent(id: string, smoothScroll?: boolean) => Promise<void>
|
||||
ion-segment-view,event,ionSegmentViewScroll,{ scrollDirection: string; scrollDistance: number; scrollDistancePercentage: number; },true
|
||||
ion-segment-view,event,ionSegmentViewScrollEnd,{ activeContentId: string; },true
|
||||
ion-segment-view,event,ionSegmentViewScrollStart,void,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
|
||||
|
||||
100
core/src/components.d.ts
vendored
100
core/src/components.d.ts
vendored
@@ -2676,6 +2676,10 @@ export namespace Components {
|
||||
* If `true`, the segment buttons will overflow and the user can swipe to see them. In addition, this will disable the gesture to drag the indicator between the buttons in order to swipe to see hidden buttons.
|
||||
*/
|
||||
"scrollable": boolean;
|
||||
/**
|
||||
* The `id` of the `segment-view` element to be associated with this segment.
|
||||
*/
|
||||
"segmentViewId"?: string;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -2690,10 +2694,15 @@ 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.
|
||||
*/
|
||||
"disabled": boolean;
|
||||
"hasIndicator": boolean;
|
||||
/**
|
||||
* Set the layout of the text and icon in the segment.
|
||||
*/
|
||||
@@ -2712,6 +2721,24 @@ export namespace Components {
|
||||
*/
|
||||
"value": SegmentValue;
|
||||
}
|
||||
interface IonSegmentContent {
|
||||
/**
|
||||
* If `true`, the segment content will not be displayed.
|
||||
*/
|
||||
"disabled": boolean;
|
||||
}
|
||||
interface IonSegmentView {
|
||||
/**
|
||||
* If `true`, the segment view cannot be interacted with.
|
||||
*/
|
||||
"disabled": boolean;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
"setContent": (id: string, smoothScroll?: boolean) => Promise<void>;
|
||||
}
|
||||
interface IonSelect {
|
||||
/**
|
||||
* The text to display on the cancel button.
|
||||
@@ -3413,6 +3440,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;
|
||||
@@ -4409,6 +4440,35 @@ declare global {
|
||||
prototype: HTMLIonSegmentButtonElement;
|
||||
new (): HTMLIonSegmentButtonElement;
|
||||
};
|
||||
interface HTMLIonSegmentContentElement extends Components.IonSegmentContent, HTMLStencilElement {
|
||||
}
|
||||
var HTMLIonSegmentContentElement: {
|
||||
prototype: HTMLIonSegmentContentElement;
|
||||
new (): HTMLIonSegmentContentElement;
|
||||
};
|
||||
interface HTMLIonSegmentViewElementEventMap {
|
||||
"ionSegmentViewScroll": {
|
||||
scrollDirection: string;
|
||||
scrollDistance: number;
|
||||
scrollDistancePercentage: number;
|
||||
};
|
||||
"ionSegmentViewScrollEnd": { activeContentId: string };
|
||||
"ionSegmentViewScrollStart": void;
|
||||
}
|
||||
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;
|
||||
@@ -4718,6 +4778,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-option": HTMLIonSelectOptionElement;
|
||||
"ion-select-popover": HTMLIonSelectPopoverElement;
|
||||
@@ -7433,6 +7495,10 @@ declare namespace LocalJSX {
|
||||
* If `true`, the segment buttons will overflow and the user can swipe to see them. In addition, this will disable the gesture to drag the indicator between the buttons in order to swipe to see hidden buttons.
|
||||
*/
|
||||
"scrollable"?: boolean;
|
||||
/**
|
||||
* The `id` of the `segment-view` element to be associated with this segment.
|
||||
*/
|
||||
"segmentViewId"?: string;
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -7447,10 +7513,15 @@ 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.
|
||||
*/
|
||||
"disabled"?: boolean;
|
||||
"hasIndicator"?: boolean;
|
||||
/**
|
||||
* Set the layout of the text and icon in the segment.
|
||||
*/
|
||||
@@ -7468,6 +7539,31 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"value"?: SegmentValue;
|
||||
}
|
||||
interface IonSegmentContent {
|
||||
/**
|
||||
* If `true`, the segment content will not be displayed.
|
||||
*/
|
||||
"disabled"?: boolean;
|
||||
}
|
||||
interface IonSegmentView {
|
||||
/**
|
||||
* If `true`, the segment view cannot be interacted with.
|
||||
*/
|
||||
"disabled"?: boolean;
|
||||
/**
|
||||
* Emitted when the segment view is scrolled.
|
||||
*/
|
||||
"onIonSegmentViewScroll"?: (event: IonSegmentViewCustomEvent<{
|
||||
scrollDirection: string;
|
||||
scrollDistance: number;
|
||||
scrollDistancePercentage: number;
|
||||
}>) => void;
|
||||
/**
|
||||
* Emitted when the segment view scroll has ended.
|
||||
*/
|
||||
"onIonSegmentViewScrollEnd"?: (event: IonSegmentViewCustomEvent<{ activeContentId: string }>) => void;
|
||||
"onIonSegmentViewScrollStart"?: (event: IonSegmentViewCustomEvent<void>) => void;
|
||||
}
|
||||
interface IonSelect {
|
||||
/**
|
||||
* The text to display on the cancel button.
|
||||
@@ -8159,6 +8255,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-option": IonSelectOption;
|
||||
"ion-select-popover": IonSelectPopover;
|
||||
@@ -8258,6 +8356,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-option": LocalJSX.IonSelectOption & JSXBase.HTMLAttributes<HTMLIonSelectOptionElement>;
|
||||
"ion-select-popover": LocalJSX.IonSelectPopover & JSXBase.HTMLAttributes<HTMLIonSelectPopoverElement>;
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
@@ -60,6 +65,8 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
|
||||
this.updateState();
|
||||
}
|
||||
|
||||
@Prop() hasIndicator = true;
|
||||
|
||||
connectedCallback() {
|
||||
const segmentEl = (this.segmentEl = this.el.closest('ion-segment'));
|
||||
if (segmentEl) {
|
||||
@@ -67,6 +74,27 @@ 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;
|
||||
}
|
||||
|
||||
// Set the disabled state of the Segment Content based on the button's disabled state
|
||||
segmentContent.disabled = this.disabled;
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
@@ -161,15 +189,11 @@ 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-background" class="segment-button-indicator-background"></div>
|
||||
</div>
|
||||
{this.hasIndicator && (
|
||||
<div part="indicator" class="segment-button-indicator segment-button-indicator-animated">
|
||||
<div part="indicator-background" class="segment-button-indicator-background"></div>
|
||||
</div>
|
||||
)}
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
@import "./segment-content";
|
||||
@import "../segment-button/segment-button.ios.vars";
|
||||
|
||||
// iOS Segment Content
|
||||
// --------------------------------------------------
|
||||
@@ -0,0 +1,5 @@
|
||||
@import "./segment-content";
|
||||
@import "../segment-button/segment-button.md.vars";
|
||||
|
||||
// Material Design Segment Content
|
||||
// --------------------------------------------------
|
||||
15
core/src/components/segment-content/segment-content.scss
Normal file
15
core/src/components/segment-content/segment-content.scss
Normal file
@@ -0,0 +1,15 @@
|
||||
// Segment Content
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
scroll-snap-align: center;
|
||||
scroll-snap-stop: always;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:host(.segment-content-disabled) {
|
||||
display: none;
|
||||
}
|
||||
31
core/src/components/segment-content/segment-content.tsx
Normal file
31
core/src/components/segment-content/segment-content.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { ComponentInterface } from '@stencil/core';
|
||||
import { Component, Host, Prop, h } from '@stencil/core';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-segment-content',
|
||||
styleUrls: {
|
||||
ios: 'segment-content.ios.scss',
|
||||
md: 'segment-content.md.scss',
|
||||
},
|
||||
shadow: true,
|
||||
})
|
||||
export class SegmentContent implements ComponentInterface {
|
||||
/**
|
||||
* If `true`, the segment content will not be displayed.
|
||||
*/
|
||||
@Prop() disabled = false;
|
||||
|
||||
render() {
|
||||
const { disabled } = this;
|
||||
|
||||
return (
|
||||
<Host
|
||||
class={{
|
||||
'segment-content-disabled': disabled,
|
||||
}}
|
||||
>
|
||||
<slot></slot>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
||||
9
core/src/components/segment-view/segment-view.ios.scss
Normal file
9
core/src/components/segment-view/segment-view.ios.scss
Normal 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;
|
||||
}
|
||||
9
core/src/components/segment-view/segment-view.md.scss
Normal file
9
core/src/components/segment-view/segment-view.md.scss
Normal 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;
|
||||
}
|
||||
29
core/src/components/segment-view/segment-view.scss
Normal file
29
core/src/components/segment-view/segment-view.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
// Segment View
|
||||
// --------------------------------------------------
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
|
||||
height: 100%;
|
||||
|
||||
overflow-x: scroll;
|
||||
scroll-snap-type: x mandatory;
|
||||
|
||||
scroll-behavior: smooth;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
153
core/src/components/segment-view/segment-view.tsx
Normal file
153
core/src/components/segment-view/segment-view.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
import type { ComponentInterface, EventEmitter } from '@stencil/core';
|
||||
import { Component, Element, Event, Host, Listen, Method, Prop, h } from '@stencil/core';
|
||||
|
||||
@Component({
|
||||
tag: 'ion-segment-view',
|
||||
styleUrls: {
|
||||
ios: 'segment-view.ios.scss',
|
||||
md: 'segment-view.md.scss',
|
||||
},
|
||||
shadow: true,
|
||||
})
|
||||
export class SegmentView implements ComponentInterface {
|
||||
private initialScrollLeft?: number;
|
||||
private previousScrollLeft = 0;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Emitted when the segment view is scrolled.
|
||||
*/
|
||||
@Event() ionSegmentViewScroll!: EventEmitter<{
|
||||
scrollDirection: string;
|
||||
scrollDistance: number;
|
||||
scrollDistancePercentage: number;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Emitted when the segment view scroll has ended.
|
||||
*/
|
||||
@Event() ionSegmentViewScrollEnd!: EventEmitter<{ activeContentId: string }>;
|
||||
|
||||
@Event() ionSegmentViewScrollStart!: EventEmitter<void>;
|
||||
|
||||
private activeContentId = '';
|
||||
|
||||
@Listen('scroll')
|
||||
handleScroll(ev: Event) {
|
||||
const { previousScrollLeft } = this;
|
||||
const { scrollLeft, offsetWidth } = ev.target as HTMLElement;
|
||||
|
||||
// Set initial scroll position if it's undefined
|
||||
// Must be a multiple of the offset width
|
||||
if (this.initialScrollLeft === undefined) {
|
||||
this.initialScrollLeft = Math.round(scrollLeft / offsetWidth) * offsetWidth;
|
||||
}
|
||||
|
||||
// Determine the scroll direction based on the previous scroll position
|
||||
const scrollDirection = scrollLeft > previousScrollLeft ? 'right' : 'left';
|
||||
this.previousScrollLeft = scrollLeft;
|
||||
|
||||
// Calculate the distance scrolled based on the initial scroll position
|
||||
// and then transform it to a percentage of the segment view width
|
||||
const scrollDistance = scrollLeft - this.initialScrollLeft;
|
||||
const scrollDistancePercentage = Math.abs(scrollDistance) / offsetWidth;
|
||||
|
||||
// Emit the scroll direction and distance
|
||||
this.ionSegmentViewScroll.emit({
|
||||
scrollDirection,
|
||||
scrollDistance,
|
||||
scrollDistancePercentage,
|
||||
});
|
||||
|
||||
// Check if the scroll is at a snapping point and return if not
|
||||
const atSnappingPoint = scrollLeft % offsetWidth === 0;
|
||||
if (!atSnappingPoint) return;
|
||||
|
||||
// Find the current segment content based on the scroll position
|
||||
const currentIndex = Math.round(scrollLeft / offsetWidth);
|
||||
|
||||
// // Update active content ID and scroll to the segment content
|
||||
const activeContent = this.getSegmentContents().filter(
|
||||
(ref) => !ref.classList.contains('segment-content-disabled')
|
||||
)[currentIndex];
|
||||
this.activeContentId = activeContent.id;
|
||||
|
||||
// Only emit scroll end event if the active content is not disabled and
|
||||
// the user is not touching the segment view
|
||||
if (activeContent?.disabled === false && !this.isTouching) {
|
||||
this.ionSegmentViewScrollEnd.emit({ activeContentId: this.activeContentId });
|
||||
this.initialScrollLeft = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle touch start event to know when the user is actively dragging the segment view.
|
||||
*/
|
||||
@Listen('touchstart')
|
||||
handleScrollStart() {
|
||||
this.ionSegmentViewScrollStart.emit();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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 } = this;
|
||||
|
||||
return (
|
||||
<Host
|
||||
class={{
|
||||
'segment-view-disabled': disabled,
|
||||
}}
|
||||
>
|
||||
<slot></slot>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
}
|
||||
134
core/src/components/segment-view/test/basic/index.html
Normal file
134
core/src/components/segment-view/test/basic/index.html
Normal file
@@ -0,0 +1,134 @@
|
||||
<!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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
</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="all">
|
||||
<ion-segment-button 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 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>
|
||||
|
||||
<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';
|
||||
}
|
||||
|
||||
segmentView.setContent(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>
|
||||
@@ -0,0 +1,92 @@
|
||||
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.click('ion-segment-button[value="top"]');
|
||||
|
||||
const segmentContent = page.locator('ion-segment-content[id="top"]');
|
||||
await expect(segmentContent).toBeInViewport();
|
||||
});
|
||||
});
|
||||
});
|
||||
124
core/src/components/segment-view/test/disabled/index.html
Normal file
124
core/src/components/segment-view/test/disabled/index.html
Normal file
@@ -0,0 +1,124 @@
|
||||
<!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 value="paid">
|
||||
<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 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="a">
|
||||
<ion-segment-button content-id="a" value="a">
|
||||
<ion-label>a</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button disabled content-id="b" value="b">
|
||||
<ion-label>b</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button disabled content-id="c" value="c">
|
||||
<ion-label>c</ion-label>
|
||||
</ion-segment-button>
|
||||
<ion-segment-button content-id="d" value="d">
|
||||
<ion-label>d</ion-label>
|
||||
</ion-segment-button>
|
||||
</ion-segment>
|
||||
<ion-segment-view>
|
||||
<ion-segment-content id="a">a</ion-segment-content>
|
||||
<ion-segment-content id="b">b</ion-segment-content>
|
||||
<ion-segment-content id="c">c</ion-segment-content>
|
||||
<ion-segment-content id="d">d</ion-segment-content>
|
||||
</ion-segment-view>
|
||||
|
||||
<ion-segment disabled 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>
|
||||
@@ -0,0 +1,101 @@
|
||||
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.setContent(
|
||||
`
|
||||
<style>
|
||||
/* Styles are here to show the disabled state */
|
||||
ion-segment-view {
|
||||
height: 100px;
|
||||
background: #3880ff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<ion-segment-view disabled>
|
||||
<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 segment = page.locator('ion-segment-view');
|
||||
|
||||
await expect(segment).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 show the second content when first segment content is disabled', async ({ page }) => {
|
||||
await page.setContent(
|
||||
`
|
||||
<ion-segment disabled>
|
||||
<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 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 segmentContent = page.locator('ion-segment-content[id="free"]');
|
||||
await expect(segmentContent).toBeInViewport();
|
||||
});
|
||||
|
||||
test('should scroll to the third content and update the segment value when the second segment content is disabled', 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 id="paid">Paid</ion-segment-content>
|
||||
<ion-segment-content disabled 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="top"]');
|
||||
await segmentContent.scrollIntoViewIfNeeded();
|
||||
await expect(segmentContent).toBeInViewport();
|
||||
|
||||
const segment = page.locator('ion-segment');
|
||||
expect(await segment.evaluate((el: HTMLIonSegmentElement) => el.value)).toBe('top');
|
||||
});
|
||||
});
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
@@ -6,15 +6,14 @@
|
||||
|
||||
:host {
|
||||
--background: #{$segment-ios-background-color};
|
||||
|
||||
@include border-radius($segment-ios-border-radius);
|
||||
--indicator-height: 100%;
|
||||
--indicator-box-shadow: 0 0 5px rgba(0, 0, 0, 0.16) @include border-radius($segment-ios-border-radius);
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
|
||||
// Segment: Color
|
||||
// --------------------------------------------------
|
||||
|
||||
@@ -22,7 +21,6 @@
|
||||
background: #{current-color(base, 0.065)};
|
||||
}
|
||||
|
||||
|
||||
// Segment: Default Toolbar
|
||||
// --------------------------------------------------
|
||||
|
||||
@@ -37,7 +35,6 @@
|
||||
background: var(--ion-toolbar-segment-background, var(--background));
|
||||
}
|
||||
|
||||
|
||||
// Segment: Color Toolbar
|
||||
// --------------------------------------------------
|
||||
|
||||
@@ -45,3 +42,14 @@
|
||||
:host(.in-toolbar-color:not(.ion-color)) {
|
||||
background: #{current-color(contrast, 0.11)};
|
||||
}
|
||||
|
||||
.segment-indicator {
|
||||
@include position(0, 0, 0, 0);
|
||||
|
||||
padding: 0 2px;
|
||||
|
||||
.segment-indicator-background {
|
||||
transform: scale(0.95);
|
||||
border-radius: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
:host {
|
||||
--background: transparent;
|
||||
--indicator-height: 2px;
|
||||
|
||||
grid-auto-columns: minmax(auto, $segment-button-md-max-width);
|
||||
}
|
||||
@@ -23,10 +24,13 @@
|
||||
min-height: var(--min-height);
|
||||
}
|
||||
|
||||
|
||||
// Segment: Scrollable
|
||||
// --------------------------------------------------
|
||||
|
||||
:host(.segment-scrollable) ::slotted(ion-segment-button) {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.segment-indicator {
|
||||
@include position(null, 0, 0, 0);
|
||||
}
|
||||
|
||||
@@ -31,8 +31,23 @@
|
||||
contain: paint;
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.segment-indicator {
|
||||
@include transform-origin(left);
|
||||
|
||||
z-index: -1;
|
||||
|
||||
position: absolute;
|
||||
|
||||
.segment-indicator-background {
|
||||
height: var(--indicator-height);
|
||||
box-shadow: var(--indicator-box-shadow);
|
||||
background-color: var(--indicator-color);
|
||||
|
||||
transition: background-color 0.3s linear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Segment: Scrollable
|
||||
// --------------------------------------------------
|
||||
|
||||
@@ -27,10 +27,21 @@ export class Segment implements ComponentInterface {
|
||||
// Value before the segment is dragged
|
||||
private valueBeforeGesture?: SegmentValue;
|
||||
|
||||
private segmentViewEl?: HTMLIonSegmentViewElement | null = null;
|
||||
|
||||
private nextButtonIndex?: number;
|
||||
|
||||
private io?: IntersectionObserver;
|
||||
|
||||
@Element() el!: HTMLIonSegmentElement;
|
||||
|
||||
@State() activated = false;
|
||||
|
||||
/**
|
||||
* The `id` of the `segment-view` element to be associated with this segment.
|
||||
*/
|
||||
@Prop() segmentViewId?: string;
|
||||
|
||||
/**
|
||||
* The color to use from your application's color palette.
|
||||
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
|
||||
@@ -78,13 +89,31 @@ 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) {
|
||||
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 {
|
||||
this.setCheckedClasses();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `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 });
|
||||
this.scrollActiveButtonIntoView();
|
||||
|
||||
// The scroll listener should handle scrolling the active button into view as needed when there is a segment view
|
||||
if (!this.segmentViewEl) {
|
||||
this.scrollActiveButtonIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,6 +161,24 @@ export class Segment implements ComponentInterface {
|
||||
|
||||
connectedCallback() {
|
||||
this.emitStyle();
|
||||
|
||||
this.segmentViewEl = this.getSegmentView();
|
||||
|
||||
if (this.segmentViewEl) {
|
||||
// Disable each button indicator when using a segment view
|
||||
// Instead, a single indicator instance will be used
|
||||
this.getButtons().forEach((ref) => (ref.hasIndicator = false));
|
||||
|
||||
this.addIntersectionObserver();
|
||||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
this.segmentViewEl = null;
|
||||
if (this.io) {
|
||||
this.io.disconnect();
|
||||
this.io = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
@@ -191,12 +238,69 @@ export class Segment implements ComponentInterface {
|
||||
const value = this.value;
|
||||
if (value !== undefined) {
|
||||
if (this.valueBeforeGesture !== value) {
|
||||
this.emitValueChange();
|
||||
if (this.segmentViewEl) {
|
||||
this.updateSegmentView(value);
|
||||
} else {
|
||||
this.emitValueChange();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.valueBeforeGesture = undefined;
|
||||
}
|
||||
|
||||
private distanceToButton(index: number) {
|
||||
const buttons = this.getButtons();
|
||||
const button = buttons[index];
|
||||
|
||||
return button.offsetLeft;
|
||||
}
|
||||
|
||||
private addIntersectionObserver() {
|
||||
if (
|
||||
typeof (window as any) !== 'undefined' &&
|
||||
'IntersectionObserver' in window &&
|
||||
'IntersectionObserverEntry' in window &&
|
||||
'isIntersecting' in window.IntersectionObserverEntry.prototype
|
||||
) {
|
||||
this.io = new IntersectionObserver((data) => {
|
||||
/**
|
||||
* On slower devices, it is possible for an intersection observer entry to contain multiple
|
||||
* objects in the array. This happens when quickly scrolling an image into view and then out of
|
||||
* view. In this case, the last object represents the current state of the component.
|
||||
*/
|
||||
if (data[data.length - 1].isIntersecting) {
|
||||
this.updateSegmentView(this.value!, false);
|
||||
this.initIndicator();
|
||||
}
|
||||
});
|
||||
|
||||
this.io.observe(this.el);
|
||||
}
|
||||
}
|
||||
|
||||
private initIndicator() {
|
||||
writeTask(() => {
|
||||
if (this.segmentViewEl) {
|
||||
const buttons = this.getButtons();
|
||||
const activeButtonIndex = buttons.findIndex((ref) => ref.value === this.value);
|
||||
if (activeButtonIndex >= 0) {
|
||||
const activeButtonPosition = buttons[activeButtonIndex].getBoundingClientRect();
|
||||
const activeButtonStyles = getComputedStyle(buttons[activeButtonIndex]);
|
||||
const indicator = this.el.shadowRoot!.querySelector('.segment-indicator') as HTMLDivElement | null;
|
||||
if (indicator) {
|
||||
const startingX = this.distanceToButton(activeButtonIndex);
|
||||
|
||||
indicator.style.width = `${activeButtonPosition.width}px`;
|
||||
indicator.style.left = `${startingX}px`;
|
||||
|
||||
// Setting a CSS variable works around issue where background element might not be rendered yet
|
||||
this.el.style.setProperty('--indicator-color', activeButtonStyles.getPropertyValue('--indicator-color'));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits an `ionChange` event.
|
||||
*
|
||||
@@ -208,7 +312,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 +328,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;
|
||||
}
|
||||
@@ -312,6 +412,202 @@ export class Segment implements ComponentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private getSegmentView() {
|
||||
if (!this.segmentViewId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const segmentViewEl = document.getElementById(this.segmentViewId);
|
||||
|
||||
if (!segmentViewEl) {
|
||||
console.warn(`Segment: Unable to find 'ion-segment-view' with id="${this.segmentViewId}"`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (segmentViewEl.tagName !== 'ION-SEGMENT-VIEW') {
|
||||
console.warn(`Segment: Element with id="${this.segmentViewId}" is not an <ion-segment-view> element.`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return segmentViewEl as HTMLIonSegmentViewElement;
|
||||
}
|
||||
|
||||
@Listen('ionSegmentViewScroll', { target: 'body' })
|
||||
handleSegmentViewScroll(ev: CustomEvent) {
|
||||
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();
|
||||
const currentIndex = buttons.findIndex((button) => button.value === this.value);
|
||||
const currentButton = buttons[currentIndex];
|
||||
const indicator = this.el.shadowRoot!.querySelector('.segment-indicator') as HTMLDivElement | null;
|
||||
|
||||
// If no buttons are found or there is no value set then do nothing
|
||||
if (!buttons.length || this.value === undefined || !indicator) return;
|
||||
|
||||
const { scrollDistancePercentage, scrollDistance } = ev.detail;
|
||||
|
||||
const findIndexFrom = (
|
||||
array: HTMLIonSegmentButtonElement[],
|
||||
predicate: (button: HTMLIonSegmentButtonElement) => boolean,
|
||||
startIndex: number
|
||||
) => {
|
||||
for (let i = startIndex; i < array.length; i++) {
|
||||
if (predicate(array[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
const findIndexFromReverse = (
|
||||
array: HTMLIonSegmentButtonElement[],
|
||||
predicate: (button: HTMLIonSegmentButtonElement) => boolean,
|
||||
startIndex: number
|
||||
) => {
|
||||
for (let i = startIndex; i >= 0; i--) {
|
||||
if (predicate(array[i])) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
// Find the next valid button (i.e. we need to ignore any disabled buttons)
|
||||
const indexOffset = Math.ceil(scrollDistancePercentage);
|
||||
const nextIndex =
|
||||
this.nextButtonIndex ??
|
||||
(scrollDistance > 0
|
||||
? findIndexFrom(buttons, (ref) => !ref.disabled, currentIndex + indexOffset)
|
||||
: findIndexFromReverse(buttons, (ref) => !ref.disabled, currentIndex - indexOffset));
|
||||
|
||||
if (nextIndex >= 0 && nextIndex < buttons.length) {
|
||||
// Figure out the number of disabled buttons between the current and next button
|
||||
const disabledButtons = (
|
||||
nextIndex > currentIndex ? buttons.slice(currentIndex, nextIndex) : buttons.slice(nextIndex, currentIndex)
|
||||
).filter((button) => button.disabled).length;
|
||||
|
||||
// Adjust the scroll distance percentage based on the number of "views" scrolled
|
||||
// We need to do this because all subsequent calculations are based on the assumption that
|
||||
// only one view can be scrolled at a time, but this is not the case when clicking a segment button
|
||||
const adjustedScrollDistancePercentage =
|
||||
scrollDistancePercentage / (Math.abs(nextIndex - currentIndex) - disabledButtons);
|
||||
|
||||
const nextButton = buttons[nextIndex];
|
||||
const nextButtonWidth = nextButton.getBoundingClientRect().width;
|
||||
|
||||
const currentButtonWidth = currentButton.getBoundingClientRect().width;
|
||||
|
||||
// Scale the width based on the width of the next button
|
||||
const diff = nextButtonWidth - currentButtonWidth;
|
||||
const width = currentButtonWidth + diff * adjustedScrollDistancePercentage;
|
||||
const indicatorStyles = getComputedStyle(indicator);
|
||||
const indicatorPadding =
|
||||
parseFloat(indicatorStyles.paddingLeft.replace('px', '')) +
|
||||
parseFloat(indicatorStyles.paddingRight.replace('px', ''));
|
||||
indicator.style.width = `${width - indicatorPadding}px`;
|
||||
|
||||
// Translate the indicator based on the scroll distance
|
||||
const distanceToNextButton = this.distanceToButton(nextIndex);
|
||||
const distanceToCurrentButton = this.distanceToButton(currentIndex);
|
||||
indicator.style.left =
|
||||
scrollDistance > 0
|
||||
? `${
|
||||
distanceToCurrentButton +
|
||||
(distanceToNextButton - distanceToCurrentButton) * adjustedScrollDistancePercentage
|
||||
}px`
|
||||
: `${
|
||||
distanceToCurrentButton -
|
||||
(distanceToCurrentButton - distanceToNextButton) * adjustedScrollDistancePercentage
|
||||
}px`;
|
||||
|
||||
const standardize_color = (str: string) => {
|
||||
const ctx = document.createElement('canvas').getContext('2d');
|
||||
ctx!.fillStyle = str;
|
||||
return ctx!.fillStyle;
|
||||
};
|
||||
// Helper function to convert hex to RGB
|
||||
const hexToRgb = (hex: string) => {
|
||||
const bigint = parseInt(hex.slice(1), 16);
|
||||
const r = (bigint >> 16) & 255;
|
||||
const g = (bigint >> 8) & 255;
|
||||
const b = bigint & 255;
|
||||
|
||||
return { r, g, b };
|
||||
};
|
||||
// Helper function to convert RGB to hex
|
||||
const rgbToHex = (r: number, g: number, b: number) => {
|
||||
const componentToHex = (c: number) => {
|
||||
const hex = c.toString(16);
|
||||
return hex.length === 1 ? '0' + hex : hex;
|
||||
};
|
||||
|
||||
return `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;
|
||||
};
|
||||
// Function to calculate the color in between based on percentage
|
||||
const interpolateColor = (percentage = adjustedScrollDistancePercentage) => {
|
||||
const currentColor = standardize_color(getComputedStyle(currentButton).getPropertyValue('--indicator-color'));
|
||||
const nextColor = standardize_color(getComputedStyle(nextButton).getPropertyValue('--indicator-color'));
|
||||
|
||||
const rgb1 = hexToRgb(currentColor);
|
||||
const rgb2 = hexToRgb(nextColor);
|
||||
|
||||
const r = Math.round(rgb1.r + (rgb2.r - rgb1.r) * percentage);
|
||||
const g = Math.round(rgb1.g + (rgb2.g - rgb1.g) * percentage);
|
||||
const b = Math.round(rgb1.b + (rgb2.b - rgb1.b) * percentage);
|
||||
|
||||
return rgbToHex(r, g, b);
|
||||
};
|
||||
indicator.querySelector('div')!.style.backgroundColor = interpolateColor();
|
||||
|
||||
if (this.scrollable) {
|
||||
// Scroll the segment container so the indicator is always in view
|
||||
indicator.scrollIntoView({
|
||||
behavior: 'instant',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Listen('ionSegmentViewScrollEnd', { target: 'body' })
|
||||
onScrollEnd(ev: CustomEvent<{ activeContentId: string }>) {
|
||||
const dispatchedFrom = ev.target as HTMLElement;
|
||||
const segmentViewEl = this.segmentViewEl as EventTarget;
|
||||
const segmentEl = this.el;
|
||||
|
||||
if (ev.composedPath().includes(segmentViewEl) || dispatchedFrom?.contains(segmentEl)) {
|
||||
this.value = ev.detail.activeContentId;
|
||||
this.nextButtonIndex = undefined;
|
||||
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(value: SegmentValue, smoothScroll = true) {
|
||||
const buttons = this.getButtons();
|
||||
const button = buttons.find((btn) => btn.value === value);
|
||||
|
||||
// If the button does not have a contentId then there is
|
||||
// no associated segment view to update
|
||||
if (!button?.contentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.segmentViewEl) {
|
||||
this.segmentViewEl.setContent(button.contentId, smoothScroll);
|
||||
}
|
||||
}
|
||||
|
||||
private scrollActiveButtonIntoView(smoothScroll = true) {
|
||||
const { scrollable, value, el } = this;
|
||||
|
||||
@@ -472,10 +768,14 @@ export class Segment implements ComponentInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
this.value = current.value;
|
||||
|
||||
if (current !== previous) {
|
||||
this.emitValueChange();
|
||||
if (this.segmentViewEl) {
|
||||
this.nextButtonIndex = this.getButtons().findIndex((button) => button.value === current.value);
|
||||
this.updateSegmentView(current.value);
|
||||
} else {
|
||||
this.value = current.value;
|
||||
this.emitValueChange();
|
||||
}
|
||||
}
|
||||
|
||||
if (this.scrollable || !this.swipeGesture) {
|
||||
@@ -574,6 +874,11 @@ export class Segment implements ComponentInterface {
|
||||
'segment-scrollable': this.scrollable,
|
||||
})}
|
||||
>
|
||||
{this.segmentViewEl && (
|
||||
<div part="indicator" class="segment-indicator">
|
||||
<div part="indicator-background" class="segment-indicator-background"></div>
|
||||
</div>
|
||||
)}
|
||||
<slot onSlotchange={this.onSlottedItemsChange}></slot>
|
||||
</Host>
|
||||
);
|
||||
|
||||
@@ -69,6 +69,8 @@ export const DIRECTIVES = [
|
||||
d.IonSearchbar,
|
||||
d.IonSegment,
|
||||
d.IonSegmentButton,
|
||||
d.IonSegmentContent,
|
||||
d.IonSegmentView,
|
||||
d.IonSelect,
|
||||
d.IonSelectOption,
|
||||
d.IonSkeletonText,
|
||||
|
||||
@@ -1952,14 +1952,14 @@ This event will not emit when programmatically setting the `value` property.
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['color', 'disabled', 'mode', 'scrollable', 'selectOnFocus', 'swipeGesture', 'value']
|
||||
inputs: ['color', 'disabled', 'mode', 'scrollable', 'segmentViewId', 'selectOnFocus', 'swipeGesture', 'value']
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-segment',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['color', 'disabled', 'mode', 'scrollable', 'selectOnFocus', 'swipeGesture', 'value'],
|
||||
inputs: ['color', 'disabled', 'mode', 'scrollable', 'segmentViewId', 'selectOnFocus', 'swipeGesture', 'value'],
|
||||
})
|
||||
export class IonSegment {
|
||||
protected el: HTMLElement;
|
||||
@@ -1984,14 +1984,14 @@ This event will not emit when programmatically setting the `value` property.
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['disabled', 'layout', 'mode', 'type', 'value']
|
||||
inputs: ['contentId', 'disabled', 'hasIndicator', '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', 'hasIndicator', 'layout', 'mode', 'type', 'value'],
|
||||
})
|
||||
export class IonSegmentButton {
|
||||
protected el: HTMLElement;
|
||||
@@ -2005,6 +2005,63 @@ export class IonSegmentButton {
|
||||
export declare interface IonSegmentButton extends Components.IonSegmentButton {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['disabled']
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-segment-content',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['disabled'],
|
||||
})
|
||||
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'],
|
||||
methods: ['setContent']
|
||||
})
|
||||
@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', 'ionSegmentViewScrollEnd', 'ionSegmentViewScrollStart']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export declare interface IonSegmentView extends Components.IonSegmentView {
|
||||
/**
|
||||
* Emitted when the segment view is scrolled.
|
||||
*/
|
||||
ionSegmentViewScroll: EventEmitter<CustomEvent<{ scrollDirection: string; scrollDistance: number; scrollDistancePercentage: number; }>>;
|
||||
/**
|
||||
* Emitted when the segment view scroll has ended.
|
||||
*/
|
||||
ionSegmentViewScrollEnd: EventEmitter<CustomEvent<{ activeContentId: string }>>;
|
||||
|
||||
ionSegmentViewScrollStart: EventEmitter<CustomEvent<void>>;
|
||||
}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
inputs: ['cancelText', 'color', 'compareWith', 'disabled', 'expandedIcon', 'fill', 'interface', 'interfaceOptions', 'justify', 'label', 'labelPlacement', 'mode', 'multiple', 'name', 'okText', 'placeholder', 'selectedText', 'shape', 'toggleIcon', 'value'],
|
||||
methods: ['open']
|
||||
|
||||
@@ -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 defineIonSelectOption } from '@ionic/core/components/ion-select-option.js';
|
||||
import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js';
|
||||
import { defineCustomElement as defineIonSpinner } from '@ionic/core/components/ion-spinner.js';
|
||||
@@ -1816,14 +1818,14 @@ export declare interface IonRow extends Components.IonRow {}
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonSegmentButton,
|
||||
inputs: ['disabled', 'layout', 'mode', 'type', 'value']
|
||||
inputs: ['contentId', 'disabled', 'hasIndicator', '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', 'hasIndicator', 'layout', 'mode', 'type', 'value'],
|
||||
standalone: true
|
||||
})
|
||||
export class IonSegmentButton {
|
||||
@@ -1838,6 +1840,67 @@ export class IonSegmentButton {
|
||||
export declare interface IonSegmentButton extends Components.IonSegmentButton {}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonSegmentContent,
|
||||
inputs: ['disabled']
|
||||
})
|
||||
@Component({
|
||||
selector: 'ion-segment-content',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: '<ng-content></ng-content>',
|
||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||
inputs: ['disabled'],
|
||||
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'],
|
||||
methods: ['setContent']
|
||||
})
|
||||
@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', 'ionSegmentViewScrollEnd', 'ionSegmentViewScrollStart']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export declare interface IonSegmentView extends Components.IonSegmentView {
|
||||
/**
|
||||
* Emitted when the segment view is scrolled.
|
||||
*/
|
||||
ionSegmentViewScroll: EventEmitter<CustomEvent<{ scrollDirection: string; scrollDistance: number; scrollDistancePercentage: number; }>>;
|
||||
/**
|
||||
* Emitted when the segment view scroll has ended.
|
||||
*/
|
||||
ionSegmentViewScrollEnd: EventEmitter<CustomEvent<{ activeContentId: string }>>;
|
||||
|
||||
ionSegmentViewScrollStart: EventEmitter<CustomEvent<void>>;
|
||||
}
|
||||
|
||||
|
||||
@ProxyCmp({
|
||||
defineCustomElementFn: defineIonSelectOption,
|
||||
inputs: ['disabled', 'value']
|
||||
|
||||
@@ -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 defineIonSelectOption } from '@ionic/core/components/ion-select-option.js';
|
||||
import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js';
|
||||
@@ -130,6 +132,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 IonSelectOption = /*@__PURE__*/createReactComponent<JSX.IonSelectOption, HTMLIonSelectOptionElement>('ion-select-option', undefined, undefined, defineIonSelectOption);
|
||||
export const IonSkeletonText = /*@__PURE__*/createReactComponent<JSX.IonSkeletonText, HTMLIonSkeletonTextElement>('ion-skeleton-text', undefined, undefined, defineIonSkeletonText);
|
||||
|
||||
@@ -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 defineIonSelectOption } from '@ionic/core/components/ion-select-option.js';
|
||||
import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js';
|
||||
@@ -730,6 +732,7 @@ export const IonSearchbar = /*@__PURE__*/ defineContainer<JSX.IonSearchbar, JSX.
|
||||
|
||||
|
||||
export const IonSegment = /*@__PURE__*/ defineContainer<JSX.IonSegment, JSX.IonSegment["value"]>('ion-segment', defineIonSegment, [
|
||||
'segmentViewId',
|
||||
'color',
|
||||
'disabled',
|
||||
'scrollable',
|
||||
@@ -744,14 +747,29 @@ 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',
|
||||
'value'
|
||||
'value',
|
||||
'hasIndicator'
|
||||
],
|
||||
'value', 'ion-change');
|
||||
|
||||
|
||||
export const IonSegmentContent = /*@__PURE__*/ defineContainer<JSX.IonSegmentContent>('ion-segment-content', defineIonSegmentContent, [
|
||||
'disabled'
|
||||
]);
|
||||
|
||||
|
||||
export const IonSegmentView = /*@__PURE__*/ defineContainer<JSX.IonSegmentView>('ion-segment-view', defineIonSegmentView, [
|
||||
'disabled',
|
||||
'ionSegmentViewScroll',
|
||||
'ionSegmentViewScrollEnd',
|
||||
'ionSegmentViewScrollStart'
|
||||
]);
|
||||
|
||||
|
||||
export const IonSelect = /*@__PURE__*/ defineContainer<JSX.IonSelect, JSX.IonSelect["value"]>('ion-select', defineIonSelect, [
|
||||
'cancelText',
|
||||
'color',
|
||||
|
||||
Reference in New Issue
Block a user