From 00c378f0f5acbd9d9fe5f965eef26f6dfaef63f3 Mon Sep 17 00:00:00 2001 From: Brandy Carney Date: Wed, 18 Sep 2024 15:32:48 -0400 Subject: [PATCH] refactor(segment): link the button and content with content-id and id --- core/api.txt | 2 + core/src/components.d.ts | 9 ++ .../segment-button/segment-button.tsx | 5 ++ .../components/segment-view/segment-view.scss | 2 + .../components/segment-view/segment-view.tsx | 88 ++++++++----------- .../segment-view/test/basic/index.html | 43 ++++++--- core/src/components/segment/segment.tsx | 19 ++++ packages/angular/src/directives/proxies.ts | 5 +- .../standalone/src/directives/proxies.ts | 7 +- packages/vue/src/proxies.ts | 1 + 10 files changed, 111 insertions(+), 70 deletions(-) diff --git a/core/api.txt b/core/api.txt index 9e15366577..98e4f3bffc 100644 --- a/core/api.txt +++ b/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 @@ -1609,6 +1610,7 @@ ion-segment-button,part,native ion-segment-content,shadow ion-segment-view,shadow +ion-segment-view,method,setContent,setContent(id: string) => Promise ion-select,shadow ion-select,prop,cancelText,string,'Cancel',false,false diff --git a/core/src/components.d.ts b/core/src/components.d.ts index 33f2f13ad3..a5e386fcbb 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -2690,6 +2690,10 @@ export namespace Components { "value"?: SegmentValue; } interface IonSegmentButton { + /** + * The `id` of the segment content. + */ + "contentId"?: string; /** * If `true`, the user cannot interact with the segment button. */ @@ -2715,6 +2719,7 @@ export namespace Components { interface IonSegmentContent { } interface IonSegmentView { + "setContent": (id: string) => Promise; } interface IonSelect { /** @@ -7465,6 +7470,10 @@ declare namespace LocalJSX { "value"?: SegmentValue; } interface IonSegmentButton { + /** + * The `id` of the segment content. + */ + "contentId"?: string; /** * If `true`, the user cannot interact with the segment button. */ diff --git a/core/src/components/segment-button/segment-button.tsx b/core/src/components/segment-button/segment-button.tsx index 26954fb6cb..c1dc1d2914 100644 --- a/core/src/components/segment-button/segment-button.tsx +++ b/core/src/components/segment-button/segment-button.tsx @@ -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. */ diff --git a/core/src/components/segment-view/segment-view.scss b/core/src/components/segment-view/segment-view.scss index eef3aa34c6..b87b646341 100644 --- a/core/src/components/segment-view/segment-view.scss +++ b/core/src/components/segment-view/segment-view.scss @@ -4,6 +4,8 @@ :host { display: flex; + height: 100%; + overflow-x: scroll; scroll-snap-type: x mandatory; diff --git a/core/src/components/segment-view/segment-view.tsx b/core/src/components/segment-view/segment-view.tsx index 322efbc894..3646013ca4 100644 --- a/core/src/components/segment-view/segment-view.tsx +++ b/core/src/components/segment-view/segment-view.tsx @@ -1,6 +1,5 @@ import type { ComponentInterface } from '@stencil/core'; -import { Component, Element, Host, Listen, h } from '@stencil/core'; -import { addEventListener, removeEventListener } from '@utils/helpers'; +import { Component, Element, Host, Listen, Method, h } from '@stencil/core'; @Component({ tag: 'ion-segment-view', @@ -8,76 +7,59 @@ import { addEventListener, removeEventListener } from '@utils/helpers'; shadow: true, }) export class SegmentView implements ComponentInterface { - private segmentEl: HTMLIonSegmentElement | null = null; - @Element() el!: HTMLElement; @Listen('scroll') - segmentViewScroll(ev: any) { - const { segmentEl } = this; + handleScroll(ev: any) { + 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(ev.target.scrollLeft / ev.target.offsetWidth); - const segmentButton = this.getSegmentButtonAtIndex(index); + const index = Math.round(scrollLeft / offsetWidth); + const segmentContent = this.getSegmentContentAtIndex(index); - if (segmentEl) { - segmentEl.value = segmentButton.value; - } + if (segmentContent === null || segmentContent === undefined) { + return; + } + + const segmentButton = this.getSegmentButtonById(segmentContent.id) as HTMLIonSegmentButtonElement; + const segment = this.getParentSegment(segmentButton); + + if (segment) { + segment.value = segmentButton.value; } } - connectedCallback() { - const segmentEl = (this.segmentEl = document.querySelector(`ion-segment[view=${this.el.id}]`)); - if (segmentEl) { - addEventListener(segmentEl, 'ionChange', this.updateSection); - } - } + @Method() + async setContent(id: string) { + const contents = this.getSegmentContents(); + const index = contents.findIndex((content) => content.id === id); - disconnectedCallback() { - const segmentEl = this.segmentEl; - if (segmentEl) { - removeEventListener(segmentEl, 'ionChange', this.updateSection); - this.segmentEl = null; - } - } + if (index === -1) return; - private updateSection = () => { - const { segmentEl } = this; - - if (segmentEl) { - const value = segmentEl.value; - const index = this.getSegmentButtonIndexWithValue(value); - this.setSection(index); - } - }; - - private setSection = (index: number) => { - const sectionWidth = this.el.offsetWidth; + const contentWidth = this.el.offsetWidth; this.el.scrollTo({ top: 0, - left: index * sectionWidth, + left: index * contentWidth, behavior: 'smooth', }); - }; - - private getSegmentButtons(): HTMLIonSegmentButtonElement[] { - const { segmentEl } = this; - - if (!segmentEl) { - return []; - } - - return Array.from(segmentEl.querySelectorAll('ion-segment-button')); } - private getSegmentButtonAtIndex(index: number) { - return this.getSegmentButtons()[index]; + private getSegmentContents(): HTMLIonSegmentContentElement[] { + return Array.from(this.el.querySelectorAll('ion-segment-content')); } - private getSegmentButtonIndexWithValue(value: any) { - return this.getSegmentButtons().findIndex((b) => b.value === value); + private getSegmentContentAtIndex(index: number) { + return this.getSegmentContents()[index]; + } + + private getSegmentButtonById(id: string) { + return document.querySelector(`ion-segment-button[content-id="${id}"]`); + } + + private getParentSegment(button: Element) { + return button.closest('ion-segment'); } render() { diff --git a/core/src/components/segment-view/test/basic/index.html b/core/src/components/segment-view/test/basic/index.html index 1b4c46bb17..600cbe654c 100644 --- a/core/src/components/segment-view/test/basic/index.html +++ b/core/src/components/segment-view/test/basic/index.html @@ -12,37 +12,56 @@ + + - + Segment View - Basic + + - - + + Paid - + Free - + Top - - - - - Paid - Free - Top + + + Free + Top + + + + Footer + + diff --git a/core/src/components/segment/segment.tsx b/core/src/components/segment/segment.tsx index 4963b40c29..0b65626733 100644 --- a/core/src/components/segment/segment.tsx +++ b/core/src/components/segment/segment.tsx @@ -307,6 +307,7 @@ export class Segment implements ComponentInterface { this.value = current.value; this.setCheckedClasses(); + this.updateSegmentView(); } 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) { const { scrollable, value, el } = this; diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index b68949142f..35db39dcf1 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -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', 'layout', 'mode', 'type', 'value'] }) @Component({ selector: 'ion-segment-button', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'layout', 'mode', 'type', 'value'], + inputs: ['contentId', 'disabled', 'layout', 'mode', 'type', 'value'], }) export class IonSegmentButton { protected el: HTMLElement; @@ -2027,6 +2027,7 @@ export declare interface IonSegmentContent extends Components.IonSegmentContent @ProxyCmp({ + methods: ['setContent'] }) @Component({ selector: 'ion-segment-view', diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index 62c918ea8a..42cfea2f10 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -1818,14 +1818,14 @@ export declare interface IonRow extends Components.IonRow {} @ProxyCmp({ defineCustomElementFn: defineIonSegmentButton, - inputs: ['disabled', 'layout', 'mode', 'type', 'value'] + inputs: ['contentId', 'disabled', 'layout', 'mode', 'type', 'value'] }) @Component({ selector: 'ion-segment-button', changeDetection: ChangeDetectionStrategy.OnPush, template: '', // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property - inputs: ['disabled', 'layout', 'mode', 'type', 'value'], + inputs: ['contentId', 'disabled', 'layout', 'mode', 'type', 'value'], standalone: true }) export class IonSegmentButton { @@ -1864,7 +1864,8 @@ export declare interface IonSegmentContent extends Components.IonSegmentContent @ProxyCmp({ - defineCustomElementFn: defineIonSegmentView + defineCustomElementFn: defineIonSegmentView, + methods: ['setContent'] }) @Component({ selector: 'ion-segment-view', diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index 4e2b1c624f..07979e8c03 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -746,6 +746,7 @@ export const IonSegment = /*@__PURE__*/ defineContainer('ion-segment-button', defineIonSegmentButton, [ + 'contentId', 'disabled', 'layout', 'type',