merge release-9.0.0 - #31374
Merged
Merged
Conversation
Syncing Major 9.0 with main
This PR just serves to update major-9.0 to reflect the current state of main
Sync major-9.0 with main after v8.8.3 release.
…k per outlet (#31055) Issue number: resolves internal --------- ## What is the current behavior? The swipe-to-go-back gesture on ion-router-outlet is controlled differently across frameworks: - React and Vue check the `swipeBackEnabled` config option on every swipe attempt in their framework wrappers (`StackManager.canStart()` and `IonRouterOutlet.canStart()`) - Angular reads the config once at mount and controls the gesture through its directive's `swipeGesture` setter - There is no public, per-outlet property to enable or disable the gesture. The only mechanism is the global `swipeBackEnabled` config option ## What is the new behavior? `ion-router-outlet` now exposes a public `swipeGesture` property in core that controls the swipe-to-go-back gesture per outlet instance. It defaults to true in "ios" mode and false in "md" mode, matching existing behavior. - The `swipeBackEnabled` config is read once when the outlet mounts (as the default for `swipeGesture`) rather than checked on every swipe attempt - The config check has been removed from React's StackManager and Vue's IonRouterOutlet -- core now owns this state - Angular's directive forwards the value to the core component's `swipeGesture` property for consistency - Apps can disable the gesture on a specific outlet: `<IonRouterOutlet swipeGesture={false} />` - Apps that set `swipeBackEnabled` once at startup require no changes ## Does this introduce a breaking change? - [X] Yes - [ ] No ## Other information I tried to align the updates to the BREAKING.md documents with the RR6 PR's changes to it to prevent issues from merging as much as possible
## What is the current behavior? Ionic Framework uses React 17 and Stencil react-output-target 0.5.3. ## What is the new behavior? Updated to React 18 and Stencil react-output-target 1.5.0. - Types updated for compatibility with type changes in React 18. - `@testing-library/react-hooks` was moved to `@testing-library/react` so this dependency has been removed. - `reflect: true` added to `value` prop on ion-radio. Props without reflection enabled are not rendered in HTML with updated react-output-target. Issue: stenciljs/output-targets#476 BREAKING CHANGE: Drops support for React 17. Applications will need to update to React 18 to be officially supported by Ionic Framework.
…31078) Issue number: internal Co-authored-by: Brandy Smith <brandyscarney@users.noreply.github.com>
Issue number: resolves #24177 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? Currently, Ionic Framework React Router only supports React Router 5. This has many issues and unsupported/broken features. ## What is the new behavior? With this change, Ionic Framework will support React Router 6 while still supporting transitions in the same way a native app does. Most of what caused this change to take a long time is that React Router 5 and React Router 6 have fundamental differences in how they handle components once they're no longer part of the view. In this change, we move away from relying on React Router directly so much and have our own implementation for deciding how views get dealt with during navigation and when they're cleaned up, allowing for us to still transition between them like we need to while still using React Router as much as we possibly can. This change will also lay the foundation for the migration to React Router 7, which will ideally be easier since most of the hard work has been dealt with here. ## Does this introduce a breaking change? - [X] Yes - [ ] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer for more information. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Current dev build (last updated 2026-04-27): > **⚠️ WARNING:** If you're going to use this dev build on an existing react project, you'll need to migrate to React Router 6. Migrating a large project at this point might be a bad idea since this will not release until v9, which will require further migrations and have other breaking changes! I have a preview of migration documentation for this [here](https://ionic-docs-git-v9-react-router-ionic1.vercel.app/docs/updating/9-0#react-router). ``` 8.8.4-dev.11777318673.18d001f6 ``` The dev build linked above will be the last one for this branch alone. Everything going forward will be the major-9.0 branch, which may include large breaking changes. --------- Co-authored-by: Maria Hutt <thetaPC@users.noreply.github.com> Co-authored-by: Sean Perkins <13732623+sean-perkins@users.noreply.github.com>
Issue number: internal --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? `@stencil/react-output-target` is pinned to exactly `1.5.0` in `core/package.json` and `packages/react/package.json`. This was a defensive pin after `1.5.1` shipped a regression that dropped the `Partial<C>` wrapper from `StencilProps`, which broke everything in our react apps by [requiring all props to be defined](https://github.com/ionic-team/ionic-framework/actions/runs/25007199736/job/73233599812?pr=30831). Exact-pinning meant we couldn't pick up future patches in the `1.5.x` line. ## What is the new behavior? Both `package.json` ranges move to `^1.5.2`. Upstream fully reverted [PR #788](stenciljs/output-targets#788) in [`1.5.2`](https://github.com/stenciljs/output-targets/blob/main/packages/react/CHANGELOG.md), so the generator emits `Components.${tag}` again and the runtime restores `Partial<C>`. Regenerating `packages/react/src/components/components.ts` against the `1.5.2` generator and running prettier produces a file byte-identical to the one committed under `1.5.0`, so the published shape is unchanged. ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer for more information. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> The original break, the upstream conversation, and the rationale for staying on the `1.5.x` line (rather than taking the new "errors on truly required props" behavior) are all in the [PR #788 thread](stenciljs/output-targets#788).
# Conflicts: # packages/react-router/package-lock.json # packages/react/package-lock.json # packages/react/package.json
Sync major-9.0 with main.
…ation (#31105) Issue number: internal --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? Currently, reviewing react router based PRs isn't possible using the vercel preview link ## What is the new behavior? This PR adds support for the preview link to support react router 6 and tries to fix some weird edge cases where navigating to a route would sometimes not show the tests in that route ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer for more information. --> ## Other information The new React Router preview app shares the React package gate (`REACT_PKG_OK`) because `@ionic/react-router` is built inside `build_react_pkgs()`. Test app builds run in parallel under that gate, then `wait` reports per-app failures. Preview URLs (once Vercel builds this branch): - React Router test app: https://ionic-framework-git-chore-vercel-add-rr-ionic1.vercel.app/react-router/ - Dir-index nav fix demo: https://ionic-framework-git-chore-vercel-add-rr-ionic1.vercel.app/src/components/progress-bar/test (no trailing slash; clicking `basic/`, `buffer/`, `determinate/` should now land on the test page instead of the root landing)
Sync major-9.0 with main.
Ionic's vue-output-target uses Stencil's vue-output-target version 0.10.7 and 0.10.8. - Update to `@stencil/vue-output-target@0.13.1`. - Change Vue model event names from kebab-case to camelCase. Stencil vue-output-target no longer converts them automatically. - Generated proxies file has been updated by latest vue-output-target.
# Conflicts: # packages/react-router/package-lock.json # packages/react/package-lock.json # packages/react/package.json # packages/vue/package-lock.json # packages/vue/package.json
Sync major-9.0 with main.
Issue number: internal --------- ## What is the current behavior? `@ionic/vue-router` and `@ionic/vue` build against vue-router 4 ## What is the new behavior? Bumps `vue-router` to `^5.0.6` and `vue` to `^3.5.0` (vue-router 5 raises its peer to `^3.5.0`). We also added Playwright tests for Vue router that are in full parity with the previous Jest and removed the Jest tests and replaced them with Playwright. This PR also makes the current Vue test app, which is also used for the Vue Router automated tests, get rebuilt with the current PR version for testing in the Vercel preview links. ## Does this introduce a breaking change? - [X] Yes - [ ] No Consumer apps that pin `vue-router` themselves need to upgrade to `^5.0.0`, and apps that explicitly pin `vue` need to bump to `^3.5.0` ## Other information CI changes: CI no longer runs `npm run test.spec` (the script and Jest devDeps are gone), and now runs playwright tests Preview (Vue + Vue Router test app, demos both packages from this PR): https://ionic-framework-git-feat-vue-router-upgrade-ionic1.vercel.app/vue/
…31134) Issue number: internal --------- ## What is the current behavior? After the user navigates back in browser history and then pushes a new route, `ion-back-button` on a tab child page silently fails. ## What is the new behavior? When the computed history delta would be non-negative, fall back to `router.replace` on `prevInfo.pathname` (or to `defaultHref` if `prevInfo` has no pathname). `incomingRouteParams` was already set to a pop/back above, so the view stack still animates the transition as a back navigation, and the params are consumed by the resulting `afterEach` hook instead of leaking into the next navigation. ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information --------- Co-authored-by: Maria Hutt <thetaPC@users.noreply.github.com>
…ne (#31137) Issue number: resolves #24074 --------- ## What is the current behavior? In vue-router, a `router.push` performed with `routerDirection="none"` doesn't set `pushedByRoute` on the resulting route info. The next page's `ion-back-button` then can't find a previous entry and falls back to `defaultHref` instead of going back through history. The same bug was previously fixed for react-router in v6, this is the vue version of the fix (Angular doesn't need this fix because the router works very differently) ## What is the new behavior? `createIonRouter` now sets `pushedByRoute` from the leaving location when the navigation is `routerAction === "push"`, `routerDirection === "none"`, and stays within the same tab context. Cross-tab pushes with direction `none` still go through the existing tab-aware branch, which has different `pushedByRoute` semantics After the fix, `ion-back-button` returns to the prior page through history and only falls back to `defaultHref` when there's genuinely no history to pop. ## Does this introduce a breaking change? - [ ] Yes - [X] No ## Other information New Playwright spec `direction-none-back.spec.ts` covers both the `forward` and `none` paths and asserts the back button lands on Page A, not the `defaultHref` fallback [Test Page](https://ionic-framework-git-fw-7145-ionic1.vercel.app/vue/direction-none-back/a)
<!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Stencil nightly build uses the Stencil react-output-target version specified in Ionic's `package.json`s. ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> Install latest Stencil react-output-target in nightly build. - Run `npm run lint` in `packages/react` to format generated `compontents.ts`. Otherwise it will be reported by `git diff`. ## Does this introduce a breaking change? - [ ] Yes - [X] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer for more information. --> --------- Co-authored-by: Shane <shane@shanessite.net>
…#31145) Issue number: resolves #29413 --------- ## What is the current behavior? In `viewStacks.ts`, `unmountLeavingViews` and `mountIntermediaryViews` walk the outlet's view stack using `startIndex - delta` (or `startIndex + delta`) as the loop end, with no bound on `viewStack.length`. `delta` comes from the popstate event's history delta. Apps that mount `<ion-tabs>` at the root with no outer `<ion-router-outlet>` only have one outlet registered, so `usingLinearNavigation` is true and these helpers actually run. Each tab switch adds a browser history entry but reuses existing view items, so `|delta|` can easily exceed the stack depth above the entering view. The loop then reads `viewStack[i]` as `undefined` and throws `TypeError: viewItem is undefined` from `viewItem.mount = false`. The navigation aborts mid-transition, which is what surfaces the secondary `enteringEl is undefined` warning and leaves that route stuck ## What is the new behavior? Both helpers bail when the entering view item isn't in the stack (`startIndex === -1`) and clamp the loop end to `Math.min(viewStack.length, ...)`, so a delta that overruns the stack stops at the last real view item instead of indexing past the end. A new Vitest spec at `packages/vue/test/base/tests/unit/tabs-single-outlet.spec.ts` mounts `<ion-tabs>` as the app root with flat routes, builds up history across tabs and sub-pages, then calls `router.go(-6)`. Without the fix the spec catches the unhandled `TypeError` from `viewStacks.ts` ## Does this introduce a breaking change? - [ ] Yes - [X] No ## Other information Current dev build: ``` 8.8.7-dev.11778707797.1723d277 ```
Issue number: resolves #29705 --------- ## What is the current behavior? In a Vue tabs app, clicking the active tab button after landing directly on a tab child (browser refresh on `/tabs/tab1/child`, or browser back from an external `window.location.href`) is a silent no-op. `IonTabBar.checkActiveTab` only records the active tab's `currentHref` when there's a `prevActiveTab`, so the initial deep load leaves `currentHref` matching `originalHref` and the `originalHref !== currentHref` guard in `IonTabButton.onClick` skips `resetTab`. Even past that guard, `resetTab` issues `router.go(routeInfo.position - currentHistoryPosition)`, which resolves to `router.go(0)` when the tab's only history entry is the current page. ## What is the new behavior? `IonTabBar.checkActiveTab` now records `currentHref` on initial setup so the tab state reflects the deep-loaded URL. `resetTab` takes the tab's `originalHref` and, when the first history entry for the tab is the current entry, falls back to `handleNavigate(originalHref, "pop", "back", undefined, tab)`. The `pop` action routes through `router.replace`, so the deep-loaded child entry is replaced rather than stacked on top and no stale child entry stays in browser history. ## Does this introduce a breaking change? - [ ] Yes - [X] No ## Other information Manual test: [/vue/tabs/tab1/childone](https://ionic-framework-git-fw-6452-ionic1.vercel.app/vue/tabs/tab1/childone). Load that URL directly, click the Tab 1 button, you should land on `/vue/tabs/tab1`
#31142) Issue number: resolves #24995 --------- ## What is the current behavior? In `createIonRouter`, the replace-action branch resolves `routeInfo.routerDirection` and `routeInfo.routerAnimation` by preferring the leaving route's values over the ones the caller passed in. The leaving route wins whenever it has any value set, so `useIonRouter.navigate(url, 'forward', 'replace')` called after a back navigation ends up with `routerDirection: 'back'`, and the same call from the initial route ends up with `'none'`. The caller's explicit direction is silently discarded. ## What is the new behavior? The precedence is flipped: the caller's `routerDirection` and `routerAnimation` are kept, and the leaving route's values are only used as a fallback when nothing was provided. `forward + replace` now animates forward regardless of how the user got to the leaving page, and the default `useIonRouter.replace(url)` keeps the `'root'` direction it was constructed with instead of inheriting whatever the leaving route had recorded. ## Does this introduce a breaking change? - [ ] Yes - [X] No ## Other information
…ch (#31139) Issue number: resolves #25141 --------- ## What is the current behavior? Navigate from `/tabs/tab1` into `/tabs/tab1/childone`, click Tab 2, then click Tab 1 again. Tab 1's child page is re-activated. Press the browser back button and the URL becomes `/tabs/tab2`, but `ion-router-outlet` still shows `tab1`. URL and displayed page are out of sync. The pop branch in `handleHistoryChange` doesn't notice that the browser's linear predecessor and the in-tab back target are different, so the new routeInfo grabs its pathname from `location.path` while the outlet renders the incorrect view ## What is the new behavior? When the pop destination differs from the in-tab back target and either side is in a tab context, the router calls `handleNavigate` to replace the URL with the in-tab predecessor's pathname. The outlet keeps rendering the in-tab page and the URL now matches what you see ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information Companion to the React Router 6 fix in [#30831](#30831), which fixed the problem in Ionic React Router
…ent instance (#31150) Issue number: resolves #25013 --------- ## What is the current behavior? A `beforeRouteEnter(to, from, next)` guard that calls `next((vm) => vm.member = 5)` never runs the callback. Vue Router's `<router-view>` invokes whatever ends up in `matchedRoute.enterCallbacks[name]` from a post-flush watcher once the view's instance is set, but `IonRouterOutlet` does its own rendering and skips that step. `matchedRoute.instances.default` gets populated in `registerIonPage`, so internal guards still fire, but the queued enter callbacks sit there unused ## What is the new behavior? `createViewStacks`'s `registerIonPage` now reads `matchedRoute.enterCallbacks.default`, resets it, and runs each callback with the resolved instance. The reset happens before invocation so a callback that triggers a synchronous navigation can push fresh entries onto the next matched record without getting wiped. There's a new Vitest case in `routing.spec.ts` that navigates into a page whose `beforeRouteEnter` does `next((vm) => { vm.member = 5 })` and asserts both the callback ran with the instance and the mutation reached the DOM ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information
# Conflicts: # core/src/components/alert/alert.tsx # core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Safari-linux.png # core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Safari-linux.png # core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Safari-linux.png # core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Safari-linux.png # core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Safari-linux.png # core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Safari-linux.png # core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Safari-linux.png # core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Safari-linux.png # core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-ios-ltr-Mobile-Safari-linux.png # core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-ios-rtl-Mobile-Safari-linux.png # core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-md-ltr-Mobile-Safari-linux.png # core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-basic-md-rtl-Mobile-Safari-linux.png # core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-ios-ltr-Mobile-Safari-linux.png # core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-ios-rtl-Mobile-Safari-linux.png # core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-md-ltr-Mobile-Safari-linux.png # core/src/components/picker-legacy/test/basic/picker.e2e.ts-snapshots/picker-value-selected-md-rtl-Mobile-Safari-linux.png # packages/angular/test/base/src/app/standalone/home-page/home-page.component.html # packages/react-router/package-lock.json # packages/react/package-lock.json # packages/react/package.json # packages/react/test/base/src/App.tsx # packages/vue-router/package-lock.json # packages/vue/package-lock.json # packages/vue/package.json
Issue number: N/A
---------
## What is the current behavior?
Textareas with a floating label and a start or end slot always display
the label in the floated state, regardless of whether the textarea
contains a value.
## What is the new behavior?
- The floating label now behaves consistently regardless of whether
start or end slots are present:
- It overlays the textarea when the field is empty.
- It floats when the textarea is focused or contains a value.
- The height of textareas has been increased to ensure consistent sizing
among all textareas.
- Start slot content is now always positioned to the left of both the
label and the input.
- Additional screenshot tests have been added to verify these behavior
and layout changes.
- A follow-up ticket has been created to address the remaining UI
differences between our leading/trailing content implementation and the
`md` specification.
## Does this introduce a breaking change?
- [x] Yes
- [ ] No
**Floating Label Behavior**
Floating labels no longer automatically float when the textarea contains
slotted content. Labels float only when the textarea is focused or has a
value.
**Internal DOM Structure Changes**
The internal DOM structure has been reorganized to support floating
labels with slotted content.
Removed: `.textarea-wrapper-inner`
Added: `.textarea-control`
Renamed:
- `.start-slot-wrapper` → `.textarea-start`
- `.end-slot-wrapper` → `.textarea-end`
Restructured:
- `.label-text-wrapper` moved from `.textarea-wrapper-inner` into
`.textarea-control`
- `.native-wrapper` moved from `.textarea-wrapper-inner` into
`.textarea-control`
- `.start-slot-wrapper` moved from `.textarea-wrapper-inner` to
`.textarea-wrapper` and was renamed `.textarea-start`
- `.end-slot-wrapper` moved from `.textarea-wrapper-inner` to
`.textarea-wrapper` and was renamed `.textarea-end`
Update your selectors to account for these structural changes:
```diff
-ion-textarea .textarea-wrapper-inner .native-wrapper { }
+ion-textarea .textarea-control .native-wrapper { }
-ion-textarea .start-slot-wrapper [slot="start"] { }
+ion-textarea .textarea-start [slot="start"] { }
-ion-textarea .end-slot-wrapper [slot="end"] { }
+ion-textarea .textarea-end [slot="end"] { }
```
**Minimum Height Change**
The minimum height of textarea in Material Design (`md` mode) is now
`72px`. At the default number of rows this makes textareas the same
height regardless of the `fill` property or `labelPlacement`. Previously
the minimum height was:
| Fill | Label placement | Previous minimum height |
| ------------------ | ----------------------- | -----------------------
|
| default | `start`, `end`, `fixed` | `44px` |
| default | `floating`, `stacked` | `56px` |
| `solid`, `outline` | any | `56px` |
These were minimums, not the heights textareas actually rendered at. A
textarea with content in the `start` or `end` slots was already taller
than its minimum, so the change affects it differently. For example, a
`fill="solid"` textarea with slotted icons and buttons previously
rendered at `72px` with a `start` label and `81px` with a `floating`
label. Both are now `72px`, so that floating label case is `9px` shorter
than before rather than taller.
Because `72px` is taller than two rows of text, `rows` values below `3`
no longer change the height of the textarea in `md` mode: `rows="1"` and
`rows="2"` both render at `72px`.
If you were relying on the previous heights, or you need `rows` to
control the height, override the minimum height back. The override has
to be more specific than the component's own style, so a bare
`ion-textarea` selector will not apply. Add a custom class to the
textarea to increase specificity:
```css
/* Add a custom class to the textarea */
ion-textarea.custom {
min-height: 44px;
}
```
---------
Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com>
Co-authored-by: ionitron <hi@ionicframework.com>
Issue number: resolves #30402 --------- ## What is the current behavior? Selects with a floating label and a start or end slot always display the label in the floated state, regardless of whether the select contains a value. ## What is the new behavior? - The floating label now behaves consistently regardless of whether start or end slots are present: - It overlays the select when the field is empty. - It floats when the select is expanded or contains a value. - It displays the placeholder when it is focused or expanded but does not contain a value. - Updates the placeholder opacity when floating so it will use the correct value of `--placeholder-opacity` instead of `1`, matching the other select label placements. - Start slot content is now always positioned to the left of both the label and the select. - Additional screenshot tests have been added to verify these behavior and layout changes. - A follow-up ticket has been created to address the remaining UI differences between our leading/trailing content implementation and the `md` specification. ## Does this introduce a breaking change? - [x] Yes - [ ] No **Floating Label Behavior** Floating labels no longer automatically float when the select contains slotted content. Labels float only when the select is focused or has a value. Additionally, when using a floating label, the placeholder is only visible when the select is focused. **Internal DOM Structure Changes** The internal DOM structure has been reorganized to support floating labels with slotted content. This changes the structure and location of several exposed shadow parts. Added: - `.select-start` — `part="start"` - `.select-control` — `part="control"` - `.select-end` — `part="end"` Removed: - `.select-wrapper-inner` — `part="inner"` Restructured: - `.label-text-wrapper` remains `part="label"` but moved from `.select-wrapper` into `.select-control` - `.native-wrapper` remains `part="container"` but moved from `.select-wrapper-inner` into `.select-control` - Start slot moved from `.select-wrapper-inner` into `.select-start` (`part="start"`) - End slot moved from `.select-wrapper-inner` into `.select-end` (`part="end"`) - `.select-icon` remains `part="icon"` but its location depends on the label state: - With a start/end label, the icon is inside `.select-control` - With a floating/stacked label, the icon is inside `.select-end` Update selectors that target the exposed shadow parts to account for the new structure: If you currently target `part="inner"`, that part has been removed. Update those styles to target the new parts as appropriate. If you target `part="label"`, `part="container"`, or `part="icon"`, the part names remain unchanged, but their position in the shadow DOM has changed. This may affect styles that depend on the relationship or layout of these parts. Use the new `part="start"`, `part="control"`, and `part="end"` parts to target the new structural wrappers. --------- Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> Co-authored-by: ionitron <hi@ionicframework.com> Co-authored-by: Shane <shane.king@outsystems.com> Co-authored-by: Maria Hutt <thetaPC@users.noreply.github.com>
… code-splitting (#31303) Issue number: resolves #30114 --------- ## What is the current behavior? Any standalone components imported by one page are bundled with every page. E.g. a landing page will deliver all Ionic Framework components used by the web app, even if most of them are not used by the landing page. Because all components are imported from one file, build tools don't split the code well. ## What is the new behavior? - Components can be imported from individual files, allowing build tools to bundle some components only with certain pages. E.g. `import { IonToolbar } from '@ionic/angular/ion-toolbar'` - `@ionic/angular` is now a barrel file, maintaining backwards compatibility. - Replaced ng-packagr build system with calling ngc directly. - Changed `standalone/src` pattern to `src/standalone`. - Made `packages/angular/package.json` the `package.json` that will be used in the npm package, in line with the React and Vue packages. ## Does this introduce a breaking change? - [ ] Yes - [X] No --------- Co-authored-by: Shane <shane@shanessite.net> Co-authored-by: Shane <shane.king@outsystems.com>
Issue number: internal
---------
## What is the current behavior?
Currently, `@ionic/migrate` covers 15 of the breaking changes in the v9
upgrade guide. The Angular `moduleResolution` change and `@ionic/core`'s
new `exports` field aren't in there, and neither are about a dozen
others.
There's also a gap for vanilla apps: `detectFrameworks` only knows
`@ionic/angular`, `@ionic/react`, and `@ionic/vue`, so a vanilla app
prints "No @ionic/{angular,react,vue} dependency found" and exits. Every
migration marked `framework: 'core'` is unreachable for the apps it was
written for.
## What is the new behavior?
This PR adds 14 migrations, taking the registry to 29 (12 auto-fix, 17
report-only). The full table is in the new
`packages/migrate/docs/v9.md`. The README is version-agnostic now, so
the next major gets its own page instead of a rewrite.
We also detect `@ionic/core` as a framework, so vanilla apps run the
`core` migrations and get their pin bumped. We gate the version check on
the binding package when there's one, because an app on a v9 binding
with a stale `@ionic/core` pin is already migrated, and taking the
lowest major would re-run the single-shot import rewrite and corrupt it.
## Does this introduce a breaking change?
- [ ] Yes
- [X] No
## Other information
Note: this PR is based off of the end result of
[ionic-docs#4620](ionic-team/ionic-docs#4620).
Ten of the docs anchors these migrations link to only exist on that
branch.
Two v9 changes stay uncovered on purpose, with the reasoning in
`docs/v9.md`: React Router's `/*` suffix for nested routes, and which
components Angular 22's `OnPush` default affects. We do flag the Angular
22 version itself.
Issue number: resolves #31344 --------- ## What is the current behavior? Currently, `attachProps` assigns every prop onto the element without checking whether it has a value. Assigning a nullish value to a reflected property stringifies it, so an unset optional prop lands as `id="undefined"` and every element rendered that way shares an id. Setting the prop back to `undefined` overwrites the attribute rather than removing it. The same goes for `title`, `slot`, `lang`, and `dir`. ## What is the new behavior? We now remove the attribute after assigning, when the value was nullish and the name is a native property. That is what `@lit/react` already does in its `setProperty`, so the hand-rolled wrappers and the generated components behave the same way. ## Does this introduce a breaking change? - [ ] Yes - [X] No In fact, this version of this fix was made explicitly to avoid introducing more breaking changes at this time. ## Other information This replaces #31349, which fixed the same issue by skipping the property assignment for nullish values rather than removing the attribute afterwards. That changes what `undefined` means for a prop with a declared default. Today `<IonModal backdropDismiss={maybeUndefined} />` writes `undefined` over the `@Prop()` default of `true` so the backdrop doesn't dismiss, but with the assignment skipped the default survives and it starts dismissing. Same for `animated`, `showBackdrop`, and `keyboardClose`. It would also leave the package inconsistent, because the generated components go through `@lit/react`, which does assign, so `IonInput` and `IonButton` would disagree about what `undefined` means. Mirroring `@lit/react` avoids both, and this PR needs no BREAKING.md entry because of it. Note that the repro in #31344 uses `IonToggle`, which is generated on v9 and already passes there. This covers the hand-rolled wrappers `@lit/react` doesn't touch: the overlays, the routing proxies, and `IonApp`/`IonIcon`/`IonNav`/`IonRouterOutlet`/the tabs components. A prop removed from the JSX entirely, rather than set to `undefined`, still isn't cleared, since `attachProps` only iterates the new props. That's pre-existing and I left it alone, because three call sites pass a filtered `newProps` against the full `prevProps` so the fix isn't as simple as it would appear. Co-authored-by: Patrick Kenny github@ptmkenny.com ## Current dev build: ``` 8.8.19-dev.11786832241.150538c7 ```
Issue number: resolves #28680 --------- ## What is the current behavior? Currently, `useIonModal` and `useIonPopover` take `componentProps` as `any`, so nothing checks the props against the component being presented. Typos and missing props show up at runtime instead of at build time. This has been attempted three times. #28685 and #29058 stalled in review, and #29158 was auto-closed when the `feature-8.0` branch was deleted rather than rejected. All three were deferred on the grounds that the added strictness needed a major, which is what this branch is. The most recent community attempt, #31183, leaves `componentProps` optional, which brings back the gap [that earlier review had already settled](#29058 (comment)): a component with required props still type checks when you pass no props at all. ## What is the new behavior? Both hooks now infer `Props` from the component and check `componentProps` against it, and `componentProps` is required when the component declares required props. Passing a JSX element is unchanged and stays permissive. `Props` comes from the component alone, using `NoInfer` on `componentProps`. That part isn't cosmetic. The overlay docs pass `dismiss` back in through `componentProps`, and inferring from that argument needs the type of `dismiss` while the same statement is still declaring it, which TypeScript rejects as circular. The cost is that a component declared inline needs its props annotated. `NoInfer` lands in the published types, so `@ionic/react` now needs TypeScript 5.4. `@ionic/angular` already required 5.4, so this lines them up, and a new `react-typescript` migration raises the pin for React apps the same way `angular-typescript` does. Writing this turned up two existing mistakes that the old `any` was hiding: the root modal in the React test app was rendering with `type` and `count` undefined, and the v9 modal controller docs example didn't type check. Both are fixed. ## Does this introduce a breaking change? - [X] Yes - [ ] No Apps passing props that don't match their component will get new compile errors, which is the point of the change. The fix is to correct the call sites. Apps below TypeScript 5.4 need to upgrade, and `npx @ionic/migrate` handles the pin. Notes are in BREAKING.md and the v9 upgrade guide. ## Other information The overload shape comes from Liam's #29158, the requirement that `componentProps` can't be omitted comes from Alexander's review on #29058, and the compile-time spec pattern comes from Ren yiwei's #31183. All three are credited as co-authors on the commit. Also, I'm aware this is way too close to the release of v9. This may need to be held off until v10, which won't be too long after v9 comes out. --------- Co-authored-by: Alexander Harding <2166114+aeharding@users.noreply.github.com> Co-authored-by: Liam DeBeasi <2721089+liamdebeasi@users.noreply.github.com> Co-authored-by: Ren yiwei <85666259+SSDWGG@users.noreply.github.com>
Issue number: internal --------- ## What is the current behavior? Currently, the Angular test app's landing page renders as unstyled inline text on the Vercel preview. It relied on `AppModule`'s `IonicModule` scope for its `ion-*` directives, but `/` is only rendered by the standalone bootstrap, so `IonicModule.forRoot()`'s `defineCustomElements()` initializer never runs, and nothing registers those elements once a build tree-shakes the unused standalone proxies. ## What is the new behavior? The landing page is now standalone and imports the components it renders, so it registers its own custom elements. The route uses `loadComponent` instead of `component` to keep that standalone build out of the lazy app's eager graph. ## Does this introduce a breaking change? - [ ] Yes - [X] No ## Other information [Major 9.0's Angular Landing Page](https://ionic-framework-git-major-90-ionic1.vercel.app/angular/) [This PR's Angular Landing page link](https://ionic-framework-git-fix-angular-landing-ionic1.vercel.app/angular/)
# Conflicts: # core/src/utils/gesture/listener.ts # lerna.json # packages/react-router/package-lock.json # packages/react/package-lock.json # packages/react/package.json # packages/vue-router/package-lock.json # packages/vue/package-lock.json # packages/vue/package.json
Sync major-9.0 with main.
Issue number: internal --------- ## What is the current behavior? `@ionic/migrate` is on npm at `0.1.0`, the manual bootstrap publish that was needed to create the package page so a trusted publisher could be registered. Both `latest` and `next` point at it. ## What is the new behavior? Bumps `packages/migrate/package.json` and its lockfile to `1.0.0` so the release workflow can publish the first stable version. - Version-only change. No source, dependency, or behavior changes. - Release is dispatched separately via `release-orchestrator.yml` with `release-type: migrate`, `tag: latest`, against `major-9.0`. - `packages/migrate` stays out of `lerna.json`, so this version is independent of the framework version. Pre-flight against this branch, matching what `release-migrate.yml` runs: - `npm run lint` (tsc --noEmit): clean - `npm test`: 137 passed across 15 files - `npm run build`: clean - `npm pack --dry-run`: 106 files, `dist/` plus `package.json` and `README.md` only ## Does this introduce a breaking change? - [ ] Yes - [X] No ## Other information The publish workflow is scoped to `working-directory: ./packages/migrate` for every step, so it cannot publish core or any framework package. It also fails the run if the version is already on npm.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v9.0.0