diff --git a/core/src/components/segment-button/segment-button.tsx b/core/src/components/segment-button/segment-button.tsx index d860735f729..3a2135421c2 100644 --- a/core/src/components/segment-button/segment-button.tsx +++ b/core/src/components/segment-button/segment-button.tsx @@ -2,7 +2,7 @@ import type { ComponentInterface } from '@stencil/core'; import { Component, Element, Host, Prop, Method, State, Watch, forceUpdate, h } from '@stencil/core'; import type { ButtonInterface } from '@utils/element-interface'; import type { Attributes } from '@utils/helpers'; -import { addEventListener, removeEventListener, inheritAttributes } from '@utils/helpers'; +import { addEventListener, removeEventListener, inheritAttributes, getNextSiblingOfType } from '@utils/helpers'; import { hostContext } from '@utils/theme'; import { getIonMode } from '../../global/ionic-global'; @@ -65,7 +65,41 @@ export class SegmentButton implements ComponentInterface, ButtonInterface { this.updateState(); } - connectedCallback() { + private waitForSegmentContent(ionSegment: HTMLIonSegmentElement | null, contentId: string): Promise { + return new Promise((resolve, reject) => { + let timeoutId: NodeJS.Timeout | undefined = undefined; + let animationFrameId: number; + + const check = () => { + if (!ionSegment) { + reject(new Error(`Segment not found when looking for Segment Content`)); + return; + } + + const segmentView = getNextSiblingOfType(ionSegment); // Skip the text nodes + const segmentContent = segmentView?.querySelector( + `ion-segment-content[id="${contentId}"]` + ) as HTMLIonSegmentContentElement | null; + if (segmentContent && timeoutId) { + clearTimeout(timeoutId); // Clear the timeout if the segmentContent is found + cancelAnimationFrame(animationFrameId); + resolve(segmentContent); + } else { + animationFrameId = requestAnimationFrame(check); // Keep checking on the next animation frame + } + }; + + check(); + + // Set a timeout to reject the promise + timeoutId = setTimeout(() => { + cancelAnimationFrame(animationFrameId); + reject(new Error(`Unable to find Segment Content with id="${contentId} within 1000 ms`)); + }, 1000); + }); + } + + async connectedCallback() { const segmentEl = (this.segmentEl = this.el.closest('ion-segment')); if (segmentEl) { this.updateState(); @@ -76,12 +110,13 @@ export class SegmentButton implements ComponentInterface, ButtonInterface { // 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}".`); + let segmentContent; + try { + // Attempt to find the Segment Content by its contentId + segmentContent = await this.waitForSegmentContent(segmentEl, this.contentId); + } catch (error) { + // If no associated Segment Content exists, log an error and return + console.error('Segment Button: ', (error as Error).message); return; } diff --git a/core/src/components/segment-view/test/basic/index.html b/core/src/components/segment-view/test/basic/index.html index 69d36d4a6c0..78dec1d9ff9 100644 --- a/core/src/components/segment-view/test/basic/index.html +++ b/core/src/components/segment-view/test/basic/index.html @@ -123,6 +123,8 @@ + + @@ -158,6 +160,34 @@ segment.value = undefined; }); } + + async function addSegmentButtonAndContent() { + const segment = document.querySelector('ion-segment'); + const segmentView = document.querySelector('ion-segment-view'); + + const newButton = document.createElement('ion-segment-button'); + const newId = `new-${Date.now()}`; + newButton.setAttribute('content-id', newId); + newButton.setAttribute('value', newId); + newButton.innerHTML = 'New Button'; + + segment.appendChild(newButton); + + setTimeout(() => { + // Timeout to test waitForSegmentContent() in segment-button + const newContent = document.createElement('ion-segment-content'); + newContent.setAttribute('id', newId); + newContent.innerHTML = 'New Content'; + + segmentView.appendChild(newContent); + + // Necessary timeout to ensure the value is set after the content is added. + // Otherwise, the transition is unsuccessful and the content is not shown. + setTimeout(() => { + segment.setAttribute('value', newId); + }, 200); + }, 200); + } diff --git a/core/src/utils/helpers.ts b/core/src/utils/helpers.ts index 97681f6652a..a740ff98ac7 100644 --- a/core/src/utils/helpers.ts +++ b/core/src/utils/helpers.ts @@ -413,3 +413,14 @@ export const shallowEqualStringMap = ( return true; }; + +export const getNextSiblingOfType = (element: Element): T | null => { + let sibling = element.nextSibling; + while (sibling) { + if (sibling.nodeType === Node.ELEMENT_NODE && (sibling as T) !== null) { + return sibling as T; + } + sibling = sibling.nextSibling; + } + return null; +};