refactor(segment): link the button and content with content-id and id

This commit is contained in:
Brandy Carney
2024-09-18 15:32:48 -04:00
parent 8af4d74846
commit 00c378f0f5
10 changed files with 111 additions and 70 deletions

View File

@ -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

View File

@ -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.
*/ */

View File

@ -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.
*/ */

View File

@ -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;

View File

@ -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() {

View File

@ -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>

View File

@ -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;

View File

@ -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',

View File

@ -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',

View File

@ -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',