mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 11:17:19 +08:00
refactor(segment): link the button and content with content-id and id
This commit is contained in:
@ -1541,6 +1541,7 @@ ion-segment,css-prop,--background,ios
|
|||||||
ion-segment,css-prop,--background,md
|
ion-segment,css-prop,--background,md
|
||||||
|
|
||||||
ion-segment-button,shadow
|
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,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,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
|
ion-segment-button,prop,mode,"ios" | "md",undefined,false,false
|
||||||
@ -1609,6 +1610,7 @@ ion-segment-button,part,native
|
|||||||
ion-segment-content,shadow
|
ion-segment-content,shadow
|
||||||
|
|
||||||
ion-segment-view,shadow
|
ion-segment-view,shadow
|
||||||
|
ion-segment-view,method,setContent,setContent(id: string) => Promise<void>
|
||||||
|
|
||||||
ion-select,shadow
|
ion-select,shadow
|
||||||
ion-select,prop,cancelText,string,'Cancel',false,false
|
ion-select,prop,cancelText,string,'Cancel',false,false
|
||||||
|
9
core/src/components.d.ts
vendored
9
core/src/components.d.ts
vendored
@ -2690,6 +2690,10 @@ export namespace Components {
|
|||||||
"value"?: SegmentValue;
|
"value"?: SegmentValue;
|
||||||
}
|
}
|
||||||
interface IonSegmentButton {
|
interface IonSegmentButton {
|
||||||
|
/**
|
||||||
|
* The `id` of the segment content.
|
||||||
|
*/
|
||||||
|
"contentId"?: string;
|
||||||
/**
|
/**
|
||||||
* If `true`, the user cannot interact with the segment button.
|
* If `true`, the user cannot interact with the segment button.
|
||||||
*/
|
*/
|
||||||
@ -2715,6 +2719,7 @@ export namespace Components {
|
|||||||
interface IonSegmentContent {
|
interface IonSegmentContent {
|
||||||
}
|
}
|
||||||
interface IonSegmentView {
|
interface IonSegmentView {
|
||||||
|
"setContent": (id: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
interface IonSelect {
|
interface IonSelect {
|
||||||
/**
|
/**
|
||||||
@ -7465,6 +7470,10 @@ declare namespace LocalJSX {
|
|||||||
"value"?: SegmentValue;
|
"value"?: SegmentValue;
|
||||||
}
|
}
|
||||||
interface IonSegmentButton {
|
interface IonSegmentButton {
|
||||||
|
/**
|
||||||
|
* The `id` of the segment content.
|
||||||
|
*/
|
||||||
|
"contentId"?: string;
|
||||||
/**
|
/**
|
||||||
* If `true`, the user cannot interact with the segment button.
|
* If `true`, the user cannot interact with the segment button.
|
||||||
*/
|
*/
|
||||||
|
@ -36,6 +36,11 @@ export class SegmentButton implements ComponentInterface, ButtonInterface {
|
|||||||
|
|
||||||
@State() checked = false;
|
@State() checked = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `id` of the segment content.
|
||||||
|
*/
|
||||||
|
@Prop({ reflect: true }) contentId?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If `true`, the user cannot interact with the segment button.
|
* If `true`, the user cannot interact with the segment button.
|
||||||
*/
|
*/
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
scroll-snap-type: x mandatory;
|
scroll-snap-type: x mandatory;
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import type { ComponentInterface } from '@stencil/core';
|
import type { ComponentInterface } from '@stencil/core';
|
||||||
import { Component, Element, Host, Listen, h } from '@stencil/core';
|
import { Component, Element, Host, Listen, Method, h } from '@stencil/core';
|
||||||
import { addEventListener, removeEventListener } from '@utils/helpers';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
tag: 'ion-segment-view',
|
tag: 'ion-segment-view',
|
||||||
@ -8,76 +7,59 @@ import { addEventListener, removeEventListener } from '@utils/helpers';
|
|||||||
shadow: true,
|
shadow: true,
|
||||||
})
|
})
|
||||||
export class SegmentView implements ComponentInterface {
|
export class SegmentView implements ComponentInterface {
|
||||||
private segmentEl: HTMLIonSegmentElement | null = null;
|
|
||||||
|
|
||||||
@Element() el!: HTMLElement;
|
@Element() el!: HTMLElement;
|
||||||
|
|
||||||
@Listen('scroll')
|
@Listen('scroll')
|
||||||
segmentViewScroll(ev: any) {
|
handleScroll(ev: any) {
|
||||||
const { segmentEl } = this;
|
const { scrollLeft, offsetWidth } = ev.target;
|
||||||
|
const atSnappingPoint = scrollLeft % offsetWidth === 0;
|
||||||
|
|
||||||
const atSnappingPoint = ev.target.scrollLeft % ev.target.offsetWidth === 0;
|
if (!atSnappingPoint) return;
|
||||||
|
|
||||||
if (atSnappingPoint) {
|
const index = Math.round(scrollLeft / offsetWidth);
|
||||||
const index = Math.round(ev.target.scrollLeft / ev.target.offsetWidth);
|
const segmentContent = this.getSegmentContentAtIndex(index);
|
||||||
const segmentButton = this.getSegmentButtonAtIndex(index);
|
|
||||||
|
|
||||||
if (segmentEl) {
|
if (segmentContent === null || segmentContent === undefined) {
|
||||||
segmentEl.value = segmentButton.value;
|
return;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
const segmentButton = this.getSegmentButtonById(segmentContent.id) as HTMLIonSegmentButtonElement;
|
||||||
const segmentEl = (this.segmentEl = document.querySelector(`ion-segment[view=${this.el.id}]`));
|
const segment = this.getParentSegment(segmentButton);
|
||||||
if (segmentEl) {
|
|
||||||
addEventListener(segmentEl, 'ionChange', this.updateSection);
|
if (segment) {
|
||||||
|
segment.value = segmentButton.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectedCallback() {
|
@Method()
|
||||||
const segmentEl = this.segmentEl;
|
async setContent(id: string) {
|
||||||
if (segmentEl) {
|
const contents = this.getSegmentContents();
|
||||||
removeEventListener(segmentEl, 'ionChange', this.updateSection);
|
const index = contents.findIndex((content) => content.id === id);
|
||||||
this.segmentEl = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateSection = () => {
|
if (index === -1) return;
|
||||||
const { segmentEl } = this;
|
|
||||||
|
|
||||||
if (segmentEl) {
|
const contentWidth = this.el.offsetWidth;
|
||||||
const value = segmentEl.value;
|
|
||||||
const index = this.getSegmentButtonIndexWithValue(value);
|
|
||||||
this.setSection(index);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private setSection = (index: number) => {
|
|
||||||
const sectionWidth = this.el.offsetWidth;
|
|
||||||
this.el.scrollTo({
|
this.el.scrollTo({
|
||||||
top: 0,
|
top: 0,
|
||||||
left: index * sectionWidth,
|
left: index * contentWidth,
|
||||||
behavior: 'smooth',
|
behavior: 'smooth',
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
private getSegmentButtons(): HTMLIonSegmentButtonElement[] {
|
|
||||||
const { segmentEl } = this;
|
|
||||||
|
|
||||||
if (!segmentEl) {
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Array.from(segmentEl.querySelectorAll('ion-segment-button'));
|
private getSegmentContents(): HTMLIonSegmentContentElement[] {
|
||||||
|
return Array.from(this.el.querySelectorAll('ion-segment-content'));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSegmentButtonAtIndex(index: number) {
|
private getSegmentContentAtIndex(index: number) {
|
||||||
return this.getSegmentButtons()[index];
|
return this.getSegmentContents()[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSegmentButtonIndexWithValue(value: any) {
|
private getSegmentButtonById(id: string) {
|
||||||
return this.getSegmentButtons().findIndex((b) => b.value === value);
|
return document.querySelector(`ion-segment-button[content-id="${id}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getParentSegment(button: Element) {
|
||||||
|
return button.closest('ion-segment');
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -12,37 +12,56 @@
|
|||||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ion-segment-view {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-segment-content:nth-of-type(even) {
|
||||||
|
background: lightblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ion-segment-content:nth-of-type(odd) {
|
||||||
|
background: lightpink;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body onload="listenForEvent()">
|
<body>
|
||||||
<ion-app>
|
<ion-app>
|
||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>Segment View - Basic</ion-title>
|
<ion-title>Segment View - Basic</ion-title>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
|
||||||
|
<ion-content>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-segment view="myView" value="Paid">
|
<ion-segment value="Paid">
|
||||||
<ion-segment-button value="Paid">
|
<ion-segment-button content-id="paid" value="Paid">
|
||||||
<ion-label>Paid</ion-label>
|
<ion-label>Paid</ion-label>
|
||||||
</ion-segment-button>
|
</ion-segment-button>
|
||||||
<ion-segment-button value="Free">
|
<ion-segment-button content-id="free" value="Free">
|
||||||
<ion-label>Free</ion-label>
|
<ion-label>Free</ion-label>
|
||||||
</ion-segment-button>
|
</ion-segment-button>
|
||||||
<ion-segment-button value="Top">
|
<ion-segment-button content-id="top" value="Top">
|
||||||
<ion-label>Top</ion-label>
|
<ion-label>Top</ion-label>
|
||||||
</ion-segment-button>
|
</ion-segment-button>
|
||||||
</ion-segment>
|
</ion-segment>
|
||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
<ion-segment-view>
|
||||||
|
<ion-segment-content id="paid">Paid</ion-segment-content>
|
||||||
<ion-content>
|
<ion-segment-content id="free">Free</ion-segment-content>
|
||||||
<ion-segment-view id="myView">
|
<ion-segment-content id="top">Top</ion-segment-content>
|
||||||
<ion-segment-content> Paid </ion-segment-content>
|
|
||||||
<ion-segment-content> Free </ion-segment-content>
|
|
||||||
<ion-segment-content> Top </ion-segment-content>
|
|
||||||
</ion-segment-view>
|
</ion-segment-view>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
||||||
|
<ion-footer>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-title>Footer</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-footer>
|
||||||
</ion-app>
|
</ion-app>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -307,6 +307,7 @@ export class Segment implements ComponentInterface {
|
|||||||
|
|
||||||
this.value = current.value;
|
this.value = current.value;
|
||||||
this.setCheckedClasses();
|
this.setCheckedClasses();
|
||||||
|
this.updateSegmentView();
|
||||||
}
|
}
|
||||||
|
|
||||||
private setCheckedClasses() {
|
private setCheckedClasses() {
|
||||||
@ -322,6 +323,24 @@ export class Segment implements ComponentInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateSegmentView() {
|
||||||
|
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 content = document.getElementById(button.contentId);
|
||||||
|
const segmentView = content?.closest('ion-segment-view');
|
||||||
|
|
||||||
|
if (segmentView) {
|
||||||
|
segmentView.setContent(button.contentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private scrollActiveButtonIntoView(smoothScroll = true) {
|
private scrollActiveButtonIntoView(smoothScroll = true) {
|
||||||
const { scrollable, value, el } = this;
|
const { scrollable, value, el } = this;
|
||||||
|
|
||||||
|
@ -1984,14 +1984,14 @@ This event will not emit when programmatically setting the `value` property.
|
|||||||
|
|
||||||
|
|
||||||
@ProxyCmp({
|
@ProxyCmp({
|
||||||
inputs: ['disabled', 'layout', 'mode', 'type', 'value']
|
inputs: ['contentId', 'disabled', 'layout', 'mode', 'type', 'value']
|
||||||
})
|
})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ion-segment-button',
|
selector: 'ion-segment-button',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
template: '<ng-content></ng-content>',
|
template: '<ng-content></ng-content>',
|
||||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||||
inputs: ['disabled', 'layout', 'mode', 'type', 'value'],
|
inputs: ['contentId', 'disabled', 'layout', 'mode', 'type', 'value'],
|
||||||
})
|
})
|
||||||
export class IonSegmentButton {
|
export class IonSegmentButton {
|
||||||
protected el: HTMLElement;
|
protected el: HTMLElement;
|
||||||
@ -2027,6 +2027,7 @@ export declare interface IonSegmentContent extends Components.IonSegmentContent
|
|||||||
|
|
||||||
|
|
||||||
@ProxyCmp({
|
@ProxyCmp({
|
||||||
|
methods: ['setContent']
|
||||||
})
|
})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ion-segment-view',
|
selector: 'ion-segment-view',
|
||||||
|
@ -1818,14 +1818,14 @@ export declare interface IonRow extends Components.IonRow {}
|
|||||||
|
|
||||||
@ProxyCmp({
|
@ProxyCmp({
|
||||||
defineCustomElementFn: defineIonSegmentButton,
|
defineCustomElementFn: defineIonSegmentButton,
|
||||||
inputs: ['disabled', 'layout', 'mode', 'type', 'value']
|
inputs: ['contentId', 'disabled', 'layout', 'mode', 'type', 'value']
|
||||||
})
|
})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ion-segment-button',
|
selector: 'ion-segment-button',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
template: '<ng-content></ng-content>',
|
template: '<ng-content></ng-content>',
|
||||||
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
|
||||||
inputs: ['disabled', 'layout', 'mode', 'type', 'value'],
|
inputs: ['contentId', 'disabled', 'layout', 'mode', 'type', 'value'],
|
||||||
standalone: true
|
standalone: true
|
||||||
})
|
})
|
||||||
export class IonSegmentButton {
|
export class IonSegmentButton {
|
||||||
@ -1864,7 +1864,8 @@ export declare interface IonSegmentContent extends Components.IonSegmentContent
|
|||||||
|
|
||||||
|
|
||||||
@ProxyCmp({
|
@ProxyCmp({
|
||||||
defineCustomElementFn: defineIonSegmentView
|
defineCustomElementFn: defineIonSegmentView,
|
||||||
|
methods: ['setContent']
|
||||||
})
|
})
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ion-segment-view',
|
selector: 'ion-segment-view',
|
||||||
|
@ -746,6 +746,7 @@ export const IonSegment = /*@__PURE__*/ defineContainer<JSX.IonSegment, JSX.IonS
|
|||||||
|
|
||||||
|
|
||||||
export const IonSegmentButton = /*@__PURE__*/ defineContainer<JSX.IonSegmentButton, JSX.IonSegmentButton["value"]>('ion-segment-button', defineIonSegmentButton, [
|
export const IonSegmentButton = /*@__PURE__*/ defineContainer<JSX.IonSegmentButton, JSX.IonSegmentButton["value"]>('ion-segment-button', defineIonSegmentButton, [
|
||||||
|
'contentId',
|
||||||
'disabled',
|
'disabled',
|
||||||
'layout',
|
'layout',
|
||||||
'type',
|
'type',
|
||||||
|
Reference in New Issue
Block a user