From f8977021f96da7ff75e7292b82cda2bc93e80cfd Mon Sep 17 00:00:00 2001 From: Shane Date: Thu, 30 Apr 2026 09:27:46 -0700 Subject: [PATCH 1/7] chore(vercel): use absolute hrefs in preview dir indexes (#31106) Issue number: internal --------- ## What is the current behavior? `generate_dir_index` in `core/scripts/vercel-build.sh` writes child links like `` and an up-link ``. Vercel doesn't 308-redirect to add a trailing slash, so visiting a preview directory URL like `/src/components/progress-bar/test` (no slash) returns the index page but the browser resolves `basic/` against the parent directory. That path doesn't exist, and Vercel's fallback serves the root landing page, so navigation looks like it "circles back" to the preview home instead of opening the test scenario. ## What is the new behavior? The dir-index generator now emits absolute hrefs based on the directory's `url_path`, including the `../` up-link. Trailing-slash quirks no longer affect navigation between component test scenarios on Vercel previews. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information Test page: - https://ionic-framework-git-fix-vercel-preview-links-ionic1.vercel.app/src/components/progress-bar/test Versus the broken version on main: - https://ionic-framework-git-main-ionic1.vercel.app/src/components/progress-bar/test --- core/scripts/vercel-build.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/scripts/vercel-build.sh b/core/scripts/vercel-build.sh index daf44142dce..99c83b22adc 100755 --- a/core/scripts/vercel-build.sh +++ b/core/scripts/vercel-build.sh @@ -62,6 +62,12 @@ generate_dir_index() { # Skip if an index.html already exists (it's an actual test page) [ -f "${dir}/index.html" ] && return + # Absolute hrefs based on url_path. Vercel does not redirect to add trailing + # slashes, so a relative href like "basic/" from a URL without a trailing + # slash resolves against the parent directory and breaks navigation. + local parent_path="${url_path%/}" + parent_path="${parent_path%/*}/" + local entries="" for child in "${dir}"/*/; do [ -d "${child}" ] || continue @@ -70,7 +76,7 @@ generate_dir_index() { case "${name}" in *-snapshots|.*) continue ;; esac # Only include if there's at least one index.html somewhere underneath find "${child}" -name "index.html" -print -quit | grep -q . || continue - entries="${entries}${name}/\n" + entries="${entries}${name}/\n" done [ -z "${entries}" ] && return @@ -92,7 +98,7 @@ generate_dir_index() {

Index of ${url_path}

- ../ + ../ $(echo -e "${entries}") From 30b479a53acbc16961002df256bec358dc11e7fa Mon Sep 17 00:00:00 2001 From: Shane Date: Fri, 1 May 2026 09:26:44 -0700 Subject: [PATCH 2/7] fix(datetime): prevent hidden-state observer from tearing down ready class on initial entry (#31108) Issue number: internal --------- ## What is the current behavior? `ion-datetime` runs two IntersectionObservers: one to detect when the host becomes visible (which adds `datetime-ready`) and one to detect when it becomes hidden (which removes the class and tears down listeners). When the host mounts offscreen, both observers receive an initial "not intersecting" entry on `observe()`. The hidden-state observer treats that initial entry as a real visible-to-hidden transition, queues a `writeTask` to remove `datetime-ready`, and races the layout-based fallback (`ensureReadyIfVisible`) that adds the class after 100ms. On WebKit the remove wins often enough that the e2e test for the fallback (added in #30793 to fix #30706) had to be skipped on Mobile Safari. Anything in production that adds `datetime-ready` outside of a real `isIntersecting: true` event is exposed to the same race. ## What is the new behavior? A `hasBeenIntersecting` flag is set true only when `visibleCallback` observes `isIntersecting: true`. The hidden-state observer's teardown is gated on this flag, so the synthetic initial "not intersecting" entry is ignored. The flag is reset when the host actually transitions to hidden and on `disconnectedCallback`. The previously duplicated init-listeners + ready-class block is consolidated into a single `markReady` helper. The WebKit skip on the IO-fallback e2e test has been removed. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information The asymmetry where `ensureReadyIfVisible` (the layout fallback) deliberately does NOT set `hasBeenIntersecting` is load-bearing: the flag must reflect a real observer signal, not a fallback-driven write, otherwise the bug returns. This is called out at the guard site so future cleanups don't undo it. This test was most likely to fail in docker testing Linux Webkit with `--repeat-each=20` because it was pretty flaky. I was able to force it to fail under these conditions and, after fixing it, it no longer failed. ## Relevant Preview Link: - https://ionic-framework-git-fw-7284-ionic1.vercel.app/src/components/datetime/test/basic --- core/src/components/datetime/datetime.tsx | 43 +++++++++++++------ .../datetime/test/basic/datetime.e2e.ts | 5 +-- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index e73cd55e0b8..f36cd66718d 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -135,6 +135,12 @@ export class Datetime implements ComponentInterface { private todayParts!: DatetimeParts; private defaultParts!: DatetimeParts; private loadTimeout: ReturnType | undefined; + /** + * Set true only by `visibleCallback`. Lets `hiddenCallback` ignore the + * synthetic "not intersecting" entry IntersectionObserver fires on + * `observe()` when the host mounts offscreen. + */ + private hasBeenIntersecting = false; private prevPresentation: string | null = null; @@ -1097,6 +1103,7 @@ export class Datetime implements ComponentInterface { this.clearFocusVisible = undefined; } this.loadTimeoutCleanup(); + this.hasBeenIntersecting = false; } /** @@ -1140,8 +1147,23 @@ export class Datetime implements ComponentInterface { return; } + this.markReady(); + }; + + private markReady = () => { + if (this.el.classList.contains('datetime-ready')) { + return; + } this.initializeListeners(); + /** + * TODO FW-2793: Datetime needs a frame to ensure that it + * can properly scroll contents into view. As a result + * we hide the scrollable content until after that frame + * so users do not see the content quickly shifting. The downside + * is that the content will pop into view a frame after. Maybe there + * is a better way to handle this? + */ writeTask(() => { this.el.classList.add('datetime-ready'); }); @@ -1170,19 +1192,8 @@ export class Datetime implements ComponentInterface { return; } - this.initializeListeners(); - - /** - * TODO FW-2793: Datetime needs a frame to ensure that it - * can properly scroll contents into view. As a result - * we hide the scrollable content until after that frame - * so users do not see the content quickly shifting. The downside - * is that the content will pop into view a frame after. Maybe there - * is a better way to handle this? - */ - writeTask(() => { - this.el.classList.add('datetime-ready'); - }); + this.hasBeenIntersecting = true; + this.markReady(); }; const visibleIO = new IntersectionObserver(visibleCallback, { threshold: 0.01, root: el }); @@ -1222,6 +1233,12 @@ export class Datetime implements ComponentInterface { return; } + // Ignore the initial "not intersecting" entry IntersectionObserver fires on observe(). + if (!this.hasBeenIntersecting) { + return; + } + this.hasBeenIntersecting = false; + this.destroyInteractionListeners(); /** diff --git a/core/src/components/datetime/test/basic/datetime.e2e.ts b/core/src/components/datetime/test/basic/datetime.e2e.ts index 4865e243092..bbc6033374f 100644 --- a/core/src/components/datetime/test/basic/datetime.e2e.ts +++ b/core/src/components/datetime/test/basic/datetime.e2e.ts @@ -440,10 +440,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => */ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { test.describe(title('datetime: IO fallback'), () => { - test('should become ready even if IntersectionObserver never reports visible', async ({ page, skip }, testInfo) => { - // TODO(FW-7284): Re-enable on WebKit after determining why it fails - skip.browser('webkit', 'Wheel is not available in WebKit'); - + test('should become ready even if IntersectionObserver never reports visible', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'issue', description: 'https://github.com/ionic-team/ionic-framework/issues/30706', From 8702ab9487f0615a3e538dc0bbfd2e9884b4669c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 May 2026 11:11:35 -0400 Subject: [PATCH 3/7] chore(deps): update pozil/auto-assign-issue action to v3 (#31094) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [pozil/auto-assign-issue](https://redirect.github.com/pozil/auto-assign-issue) | action | major | `v2.2.0` → `v3.0.0` | --- ### Release Notes
pozil/auto-assign-issue (pozil/auto-assign-issue) ### [`v3.0.0`](https://redirect.github.com/pozil/auto-assign-issue/releases/tag/v3.0.0): - Switch to EcmaScript Module [Compare Source](https://redirect.github.com/pozil/auto-assign-issue/compare/v2.2.0...v3.0.0) - feat: switch to EcmaScript Module - build: bump dependencies
--- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - "every weekday before 11am" - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Never, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/ionic-team/ionic-framework). Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/assign-issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/assign-issues.yml b/.github/workflows/assign-issues.yml index d06c1f52e10..d79a8c17c2c 100644 --- a/.github/workflows/assign-issues.yml +++ b/.github/workflows/assign-issues.yml @@ -11,7 +11,7 @@ jobs: issues: write steps: - name: 'Auto-assign issue' - uses: pozil/auto-assign-issue@39c06395cbac76e79afc4ad4e5c5c6db6ecfdd2e # v2.2.0 + uses: pozil/auto-assign-issue@70adb98ca8b3941524e9ecde48e89067c4f96736 # v3.0.0 with: assignees: brandyscarney, thetaPC, ShaneK numOfAssignee: 1 From c18502f3efdec5440a11289235a93c62ce27ab89 Mon Sep 17 00:00:00 2001 From: Alexander Harding Date: Tue, 5 May 2026 17:50:11 -0500 Subject: [PATCH 4/7] fix(action-sheet): restore action-sheet-selected class on non-radio buttons (#31109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue number: resolves #31090 --------- ## What is the current behavior? Buttons configured with `role: "selected"` no longer receive the `action-sheet-selected` CSS class. Userland styling that targets `.action-sheet-selected` (the documented hook for marking the active option — bold text, custom checkmarks, etc.) silently stopped working as of `8.7.12`. This regressed in #30769 (`fix(select, action-sheet): use radio role for options`). The new render path computes the button's class as: ```tsx class={{ ...buttonClass(b), 'action-sheet-selected': isActiveRadio, }} ``` `buttonClass(b)` already emits `'action-sheet-selected': true` for `role: "selected"` (via `[action-sheet-${button.role}]]: button.role !== undefined`), but the second key with the same name overrides it. For any non-radio button `isActiveRadio` is `false`, so the class is dropped from the rendered `