Compare commits

...

10 Commits

Author SHA1 Message Date
Sean Perkins
5f56f069e5 refactor(react-router): migrate stack manager to hooks 2023-08-01 16:03:17 -04:00
Liam DeBeasi
824033f1d4 fix(react, vue): custom animations are used when going back (#27895)
Issue number: resolves #27873

---------

<!-- 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. -->

Custom animations are not always used when the `handleNavigateBack`
method is called. This impacts both Ionic React and Ionic Vue.

While we do set the `incomingRouteParams` with the animation:
a08a5894ba/packages/react-router/src/ReactRouter/IonRouter.tsx (L247-L252)

We do sometimes call `handleNavigate`:
a08a5894ba/packages/react-router/src/ReactRouter/IonRouter.tsx (L273-L279)

This `handleNavigate` method sets `incomingRouteParams` again:
a08a5894ba/packages/react-router/src/ReactRouter/IonRouter.tsx (L225-L230)

Since we do not re-pass `routeAnimation`, that field gets set to
`undefined` and the custom animation does not get used.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Custom animation is now passed to `handleNavigate`

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

Dev build: `7.2.2-dev.11690810887.180000d1`
2023-07-31 17:57:03 +00:00
Liam DeBeasi
b8553d89f8 docs(popover): clarify size property (#27894)
Issue number: N/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. -->

See: https://github.com/ionic-team/ionic-framework/issues/27877

The current description does not accurately describe what `size="auto"`
does.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Description clarifies that the width of the popover is set based on
platform defaults.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->
2023-07-31 15:25:45 +00:00
Liam DeBeasi
ba2f49b8a4 fix(radio): radios can be focused and are announced with group (#27817)
Issue number: resolves #27438

---------

<!-- 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. -->

There are a few issues with the modern radio syntax:

1. The native radio is inside the Shadow DOM. As a result, radios are
not announced with their parent group with screen readers (i.e. "1 of
3")
2. The native radio cannot be focused inside of `ion-select-popover` on
Firefox.
3. The `ionFocus` and `ionBlur` events do not fire.

I also discovered an issue with item:

1. Items inside of a Radio Group have a role of `listitem` which prevent
radios from being grouped correctly in some browsers. According to
https://bugzilla.mozilla.org/show_bug.cgi?id=1840916, browsers are
behaving correctly here. The `listitem` role should not be present when
an item is used in a radio group (even if the radio group itself is
inside a list).

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

Most of the changes are test-related, but I broke it down per commit to
make this easier to review:


ae77002afd

- Item no longer has `role="listitem"` when used inside of a radio
group.
- Added spec tests to verify the role behavior


0a9b7fb91d

- I discovered that some the legacy basic test were accidentally using
the modern syntax. I corrected this by adding `legacy="true"` to the
radios.


a8a90e53b2,
412d1d54e7,
and
1d1179b69a

- The current radio group tests only tested the legacy radio syntax, and
not the modern syntax.
- I created a `legacy` directory to house the legacy syntax tests.
- I created new tests in the root test directory for the modern syntax.
- I also deleted the screenshots for the modern tests here because the
tests for `ion-radio` already take screenshots of the radio (even in an
item).


e2c966e68b
- Moved radio roles to the host. This allows Firefox to focus radios and
for screen readers to announce the radios as part of a group.
- I also added focus/blur listeners so ionFocus and ionBlur fire


f10eff47a5

- I cleaned up the tests here to use a common radio fixture

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

I tested this with the following setups.  indicates the screen reader
announces the group count (i.e. "1 of 4").  indicates the screen reader
does not announce the group count.

**Radio in Radio Group:**
- iOS + VoiceOver: 
- Android + TalkBack: 
- macOS + VoiceOver + Safari: 
- macOS + VoiceOver + Firefox: 
- macOS + VoiceOver + Chrome: 
- Windows + NVDA + Chrome: 
- Windows + NVDA + Firefox: 

**Radio in Item in Radio Group :**
- iOS + VoiceOver: 
- Android + TalkBack: 
(https://bugs.chromium.org/p/chromium/issues/detail?id=1459006)
- macOS + VoiceOver + Safari: 
- macOS + VoiceOver + Firefox: 
- macOS + VoiceOver + Chrome: 
(https://bugs.chromium.org/p/chromium/issues/detail?id=1459003)
- Windows + NVDA + Chrome: 
- Windows + NVDA + Firefox: 
2023-07-31 14:07:44 +00:00
dependabot[bot]
a08a5894ba chore(deps-dev): Bump @stencil/sass from 3.0.4 to 3.0.5 in /core (#27850)
Bumps [@stencil/sass](https://github.com/ionic-team/stencil-sass) from
3.0.4 to 3.0.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ionic-team/stencil-sass/releases"><code>@​stencil/sass</code>'s
releases</a>.</em></p>
<blockquote>
<h2>v3.0.5</h2>
<h2>What's Changed</h2>
<h3>Bug Fixes</h3>
<ul>
<li>fix(utils): only run plugin for sass files by <a
href="https://github.com/rwaskiewicz"><code>@​rwaskiewicz</code></a> in
<a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/361">ionic-team/stencil-sass#361</a></li>
</ul>
<h3>Chores</h3>
<ul>
<li>chore(package): add engines to package.json by <a
href="https://github.com/rwaskiewicz"><code>@​rwaskiewicz</code></a> in
<a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/337">ionic-team/stencil-sass#337</a></li>
<li>chore(repo): remove dependabot by <a
href="https://github.com/rwaskiewicz"><code>@​rwaskiewicz</code></a> in
<a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/332">ionic-team/stencil-sass#332</a></li>
<li>Configure Renovate by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/330">ionic-team/stencil-sass#330</a></li>
<li>chore: replace stencil slack with discord channel by <a
href="https://github.com/sean-perkins"><code>@​sean-perkins</code></a>
in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/349">ionic-team/stencil-sass#349</a></li>
<li>chore(repo): update ci workflow post-stencil v4 by <a
href="https://github.com/rwaskiewicz"><code>@​rwaskiewicz</code></a> in
<a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/355">ionic-team/stencil-sass#355</a></li>
<li>chore(ci): don't fail fast for build job by <a
href="https://github.com/rwaskiewicz"><code>@​rwaskiewicz</code></a> in
<a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/363">ionic-team/stencil-sass#363</a></li>
<li>chore(repo): ignore .tgz files by <a
href="https://github.com/rwaskiewicz"><code>@​rwaskiewicz</code></a> in
<a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/362">ionic-team/stencil-sass#362</a></li>
</ul>
<h3>Dependency Updates</h3>
<p><code>@ionic/prettier-config</code></p>
<ul>
<li>chore(deps): update dependency <code>@​ionic/prettier-config</code>
to v3.1.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/371">ionic-team/stencil-sass#371</a></li>
<li>chore(deps): update dependency <code>@​ionic/prettier-config</code>
to v4 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/373">ionic-team/stencil-sass#373</a></li>
</ul>
<p><code>@stencil/core</code></p>
<ul>
<li>chore(deps-dev): bump <code>@​stencil/core</code> from 3.3.0 to
3.3.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/328">ionic-team/stencil-sass#328</a></li>
<li>chore(deps-dev): bump <code>@​stencil/core</code> from 3.3.1 to
3.4.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/344">ionic-team/stencil-sass#344</a></li>
<li>chore(deps): update dependency <code>@​stencil/core</code> to v3.4.1
by <a href="https://github.com/renovate"><code>@​renovate</code></a> in
<a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/354">ionic-team/stencil-sass#354</a></li>
<li>chore(deps): update dependency <code>@​stencil/core</code> to v4 by
<a href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/360">ionic-team/stencil-sass#360</a></li>
</ul>
<p><code>@types/node</code></p>
<ul>
<li>chore(deps-dev): bump <code>@​types/node</code> from 20.2.5 to
20.3.0 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/340">ionic-team/stencil-sass#340</a></li>
<li>chore(deps-dev): bump <code>@​types/node</code> from 20.3.0 to
20.3.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/343">ionic-team/stencil-sass#343</a></li>
<li>chore(deps): update dependency <code>@​types/node</code> to v20.3.2
by <a href="https://github.com/renovate"><code>@​renovate</code></a> in
<a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/359">ionic-team/stencil-sass#359</a></li>
<li>chore(deps): update dependency <code>@​types/node</code> to v20.3.3
by <a href="https://github.com/renovate"><code>@​renovate</code></a> in
<a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/365">ionic-team/stencil-sass#365</a></li>
</ul>
<p><code>actions/checkout</code></p>
<ul>
<li>chore(deps): bump actions/checkout from 3.5.2 to 3.5.3 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/335">ionic-team/stencil-sass#335</a></li>
</ul>
<p><code>actions/setup-node</code></p>
<ul>
<li>chore(deps): update actions/setup-node action to v3.7.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/367">ionic-team/stencil-sass#367</a></li>
</ul>
<p><code>jest</code></p>
<ul>
<li>chore(deps): update jest to v29 (major) by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/352">ionic-team/stencil-sass#352</a></li>
<li>chore(deps): update dependency jest to v29.6.1 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/368">ionic-team/stencil-sass#368</a></li>
</ul>
<p><code>node</code></p>
<ul>
<li>chore(deps): update node.js to v20.3.1 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/353">ionic-team/stencil-sass#353</a></li>
<li>chore(deps): update node.js to v20.4.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/369">ionic-team/stencil-sass#369</a></li>
<li>chore(deps): update node.js to v20.5.0 by <a
href="https://github.com/renovate"><code>@​renovate</code></a> in <a
href="https://redirect.github.com/ionic-team/stencil-sass/pull/375">ionic-team/stencil-sass#375</a></li>
</ul>
<p><code>np</code></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="c400e88fd0"><code>c400e88</code></a>
3.0.5</li>
<li><a
href="965c6a3c0e"><code>965c6a3</code></a>
chore(deps): update node.js to v20.5.0 (<a
href="https://redirect.github.com/ionic-team/stencil-sass/issues/375">#375</a>)</li>
<li><a
href="53ca4c57d9"><code>53ca4c5</code></a>
chore(deps): update dependency terser to v5.19.1 (<a
href="https://redirect.github.com/ionic-team/stencil-sass/issues/374">#374</a>)</li>
<li><a
href="c6d78af154"><code>c6d78af</code></a>
chore(deps): update dependency <code>@​ionic/prettier-config</code> to
v4 (<a
href="https://redirect.github.com/ionic-team/stencil-sass/issues/373">#373</a>)</li>
<li><a
href="d37a20caec"><code>d37a20c</code></a>
chore(deps): update dependency terser to v5.19.0 (<a
href="https://redirect.github.com/ionic-team/stencil-sass/issues/372">#372</a>)</li>
<li><a
href="3a6098a434"><code>3a6098a</code></a>
chore(deps): update dependency prettier to v3 (<a
href="https://redirect.github.com/ionic-team/stencil-sass/issues/370">#370</a>)</li>
<li><a
href="0063520bcf"><code>0063520</code></a>
chore(deps): update dependency <code>@​ionic/prettier-config</code> to
v3.1.0 (<a
href="https://redirect.github.com/ionic-team/stencil-sass/issues/371">#371</a>)</li>
<li><a
href="76c367d64f"><code>76c367d</code></a>
fix(utils): only run plugin for sass files (<a
href="https://redirect.github.com/ionic-team/stencil-sass/issues/361">#361</a>)</li>
<li><a
href="8d7cbc6c70"><code>8d7cbc6</code></a>
chore(repo): ignore .tgz files (<a
href="https://redirect.github.com/ionic-team/stencil-sass/issues/362">#362</a>)</li>
<li><a
href="efb2efcb4f"><code>efb2efc</code></a>
chore(ci): don't fail fast for build job (<a
href="https://redirect.github.com/ionic-team/stencil-sass/issues/363">#363</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/ionic-team/stencil-sass/compare/v3.0.4...v3.0.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@stencil/sass&package-manager=npm_and_yarn&previous-version=3.0.4&new-version=3.0.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-27 16:23:13 +00:00
Liam DeBeasi
0b8f1bc7dd fix(item-options): use correct safe area padding (#27853)
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?
<!-- Please describe the current behavior that you are modifying. -->

Item sliding options in the "start" slot use the "left" safe area
padding on the end edge. This causes the padding to be added in the
wrong place.

During development I also discovered that the RTL padding was wrong for
both start and end options.

## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

LTR:

- `side="start"` options set "left" safe area padding on left edge of
first child
- `side="end"` options set "right" safe area padding on right edge of
last child

RTL:

- `side="start"` options set "right" safe area padding on right edge of
first child
- `side="end"` options set "left" safe area padding on the left edge of
the last child

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->

| side prop/text direction | `main` | `branch` |
| - | - | - |
| start/LTR |
![start-ltr](https://github.com/ionic-team/ionic-framework/assets/2721089/36fd01c7-0907-4933-b8be-f0193f5652f3)
|
![start-ltr](https://github.com/ionic-team/ionic-framework/assets/2721089/0949fd0e-22a9-4101-bfff-07062b69fdd5)
|
| end/LTR |
![end-ltr](https://github.com/ionic-team/ionic-framework/assets/2721089/1fcc1440-e2ad-4935-9bb5-cce6d7f466ab)
|
![end-ltr](https://github.com/ionic-team/ionic-framework/assets/2721089/33c520d3-2bee-4235-a491-93a2c2d1fab5)
|
| start/RTL |
![start-rtl](https://github.com/ionic-team/ionic-framework/assets/2721089/ce66cc00-019a-4b63-b0d3-3ae094ae53a0)
|
![start-rtl](https://github.com/ionic-team/ionic-framework/assets/2721089/c655cfe7-4b22-41fb-8910-7b7055f87f9b)
|
| end/RTL |
![end-rtl](https://github.com/ionic-team/ionic-framework/assets/2721089/2d9f6810-80c3-479c-90d9-c4e0c55ad705)
|
![end-rtl](https://github.com/ionic-team/ionic-framework/assets/2721089/745a499b-bb22-40d4-a74f-55232c2af102)
|
2023-07-26 16:24:14 +00:00
Liam DeBeasi
bd71373f1a merge release-7.2.1
Release 7.2.1
2023-07-26 12:23:52 -04:00
ionitron
960adbbc5c chore(): update package lock files 2023-07-26 15:54:44 +00:00
ionitron
db29871654 v7.2.1 2023-07-26 15:54:29 +00:00
dependabot[bot]
e9ee96a443 chore(deps-dev): Bump @playwright/test from 1.36.1 to 1.36.2 in /core (#27857)
Bumps [@playwright/test](https://github.com/Microsoft/playwright) from
1.36.1 to 1.36.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/Microsoft/playwright/releases"><code>@​playwright/test</code>'s
releases</a>.</em></p>
<blockquote>
<h2>1.36.2</h2>
<h3>Highlights</h3>
<p><a
href="https://redirect.github.com/microsoft/playwright/issues/24316">microsoft/playwright#24316</a>
- [REGRESSION] Character classes are not working in globs in 1.36</p>
<h3>Browser Versions</h3>
<ul>
<li>Chromium 115.0.5790.75</li>
<li>Mozilla Firefox 115.0</li>
<li>WebKit 17.0</li>
</ul>
<p>This version was also tested against the following stable
channels:</p>
<ul>
<li>Google Chrome 114</li>
<li>Microsoft Edge 114</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="1f983acac0"><code>1f983ac</code></a>
chore: mark 1.36.2 (<a
href="https://redirect.github.com/Microsoft/playwright/issues/24379">#24379</a>)</li>
<li><a
href="40e2096fbc"><code>40e2096</code></a>
cherry-pick(<a
href="https://redirect.github.com/Microsoft/playwright/issues/24371">#24371</a>):
fix: properly handle character sets in globs</li>
<li>See full diff in <a
href="https://github.com/Microsoft/playwright/compare/v1.36.1...v1.36.2">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=@playwright/test&package-manager=npm_and_yarn&previous-version=1.36.1&new-version=1.36.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-26 13:11:41 +00:00
62 changed files with 1146 additions and 547 deletions

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
### Bug Fixes
* **item-sliding:** buttons are not interactive on close ([#27829](https://github.com/ionic-team/ionic-framework/issues/27829)) ([6e4919c](https://github.com/ionic-team/ionic-framework/commit/6e4919caff90fc60988e5cc85ad7161844eb5b51)), closes [#22722](https://github.com/ionic-team/ionic-framework/issues/22722)
* **modal:** body background is reset with inline card modals ([#27835](https://github.com/ionic-team/ionic-framework/issues/27835)) ([38626d9](https://github.com/ionic-team/ionic-framework/commit/38626d96809d1c6be523ea62a4fac1dec73ee891)), closes [#27830](https://github.com/ionic-team/ionic-framework/issues/27830)
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)

View File

@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
### Bug Fixes
* **item-sliding:** buttons are not interactive on close ([#27829](https://github.com/ionic-team/ionic-framework/issues/27829)) ([6e4919c](https://github.com/ionic-team/ionic-framework/commit/6e4919caff90fc60988e5cc85ad7161844eb5b51)), closes [#22722](https://github.com/ionic-team/ionic-framework/issues/22722)
* **modal:** body background is reset with inline card modals ([#27835](https://github.com/ionic-team/ionic-framework/issues/27835)) ([38626d9](https://github.com/ionic-team/ionic-framework/commit/38626d96809d1c6be523ea62a4fac1dec73ee891)), closes [#27830](https://github.com/ionic-team/ionic-framework/issues/27830)
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)

52
core/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/core",
"version": "7.2.0",
"version": "7.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
"version": "7.2.0",
"version": "7.2.1",
"license": "MIT",
"dependencies": {
"@stencil/core": "^3.4.0",
@@ -22,12 +22,12 @@
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@jest/core": "^27.5.1",
"@playwright/test": "^1.36.1",
"@playwright/test": "^1.36.2",
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/angular-output-target": "^0.7.1",
"@stencil/react-output-target": "^0.5.3",
"@stencil/sass": "^3.0.4",
"@stencil/sass": "^3.0.5",
"@stencil/vue-output-target": "^0.8.6",
"@types/jest": "^27.5.2",
"@types/node": "^14.6.0",
@@ -1541,13 +1541,13 @@
}
},
"node_modules/@playwright/test": {
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.1.tgz",
"integrity": "sha512-YK7yGWK0N3C2QInPU6iaf/L3N95dlGdbsezLya4n0ZCh3IL7VgPGxC6Gnznh9ApWdOmkJeleT2kMTcWPRZvzqg==",
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz",
"integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==",
"dev": true,
"dependencies": {
"@types/node": "*",
"playwright-core": "1.36.1"
"playwright-core": "1.36.2"
},
"bin": {
"playwright": "cli.js"
@@ -1655,10 +1655,14 @@
}
},
"node_modules/@stencil/sass": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.4.tgz",
"integrity": "sha512-k1dP0A2QBx62m250FATc1hErXxXs6Jnf4TBxdL1C/dc32Kzz2n5aCT4SodBz0ebT5WMnITauZyFqYxzCzDoKag==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.5.tgz",
"integrity": "sha512-9nyllMXOEvHywo6fP2iwXgnq32A+OOUE36Aq7iUjzwT3wdr04qsvupO1JNIyRvmvCDF15hOKXztrZH1/wDu2Zg==",
"dev": true,
"engines": {
"node": ">=12.0.0",
"npm": ">=6.0.0"
},
"peerDependencies": {
"@stencil/core": ">=2.0.0 || >=3.0.0-beta.0 || >= 4.0.0-beta.0 || >= 4.0.0"
}
@@ -8190,9 +8194,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.1.tgz",
"integrity": "sha512-7+tmPuMcEW4xeCL9cp9KxmYpQYHKkyjwoXRnoeTowaeNat8PoBMk/HwCYhqkH2fRkshfKEOiVus/IhID2Pg8kg==",
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz",
"integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@@ -11451,14 +11455,14 @@
}
},
"@playwright/test": {
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.1.tgz",
"integrity": "sha512-YK7yGWK0N3C2QInPU6iaf/L3N95dlGdbsezLya4n0ZCh3IL7VgPGxC6Gnznh9ApWdOmkJeleT2kMTcWPRZvzqg==",
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz",
"integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==",
"dev": true,
"requires": {
"@types/node": "*",
"fsevents": "2.3.2",
"playwright-core": "1.36.1"
"playwright-core": "1.36.2"
}
},
"@rollup/plugin-node-resolve": {
@@ -11532,9 +11536,9 @@
"requires": {}
},
"@stencil/sass": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.4.tgz",
"integrity": "sha512-k1dP0A2QBx62m250FATc1hErXxXs6Jnf4TBxdL1C/dc32Kzz2n5aCT4SodBz0ebT5WMnITauZyFqYxzCzDoKag==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.5.tgz",
"integrity": "sha512-9nyllMXOEvHywo6fP2iwXgnq32A+OOUE36Aq7iUjzwT3wdr04qsvupO1JNIyRvmvCDF15hOKXztrZH1/wDu2Zg==",
"dev": true,
"requires": {}
},
@@ -16328,9 +16332,9 @@
}
},
"playwright-core": {
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.1.tgz",
"integrity": "sha512-7+tmPuMcEW4xeCL9cp9KxmYpQYHKkyjwoXRnoeTowaeNat8PoBMk/HwCYhqkH2fRkshfKEOiVus/IhID2Pg8kg==",
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz",
"integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==",
"dev": true
},
"postcss": {

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "7.2.0",
"version": "7.2.1",
"description": "Base components for Ionic",
"keywords": [
"ionic",
@@ -44,12 +44,12 @@
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@jest/core": "^27.5.1",
"@playwright/test": "^1.36.1",
"@playwright/test": "^1.36.2",
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/angular-output-target": "^0.7.1",
"@stencil/react-output-target": "^0.5.3",
"@stencil/sass": "^3.0.4",
"@stencil/sass": "^3.0.5",
"@stencil/vue-output-target": "^0.8.6",
"@types/jest": "^27.5.2",
"@types/node": "^14.6.0",

View File

@@ -2176,7 +2176,7 @@ export namespace Components {
*/
"side": PositionSide;
/**
* Describes how to calculate the popover width. If `"cover"`, the popover width will match the width of the trigger. If `"auto"`, the popover width will be determined by the content in the popover.
* Describes how to calculate the popover width. If `"cover"`, the popover width will match the width of the trigger. If `"auto"`, the popover width will be set to a static default value.
*/
"size": PopoverSize;
/**
@@ -6189,7 +6189,7 @@ declare namespace LocalJSX {
*/
"side"?: PositionSide;
/**
* Describes how to calculate the popover width. If `"cover"`, the popover width will match the width of the trigger. If `"auto"`, the popover width will be determined by the content in the popover.
* Describes how to calculate the popover width. If `"cover"`, the popover width will match the width of the trigger. If `"auto"`, the popover width will be set to a static default value.
*/
"size"?: PopoverSize;
/**

View File

@@ -56,15 +56,17 @@ ion-item-options {
}
}
.item-options-start ion-item-option:first-child {
@include padding-horizontal(null, var(--ion-safe-area-left));
/* stylelint-disable property-disallowed-list */
[dir="ltr"] .item-options-start ion-item-option:first-child,
[dir="rtl"] .item-options-start ion-item-option:last-child {
padding-left: var(--ion-safe-area-left);
}
.item-options-end ion-item-option:last-child {
@include padding-horizontal(null, var(--ion-safe-area-right));
[dir="ltr"] .item-options-end ion-item-option:last-child,
[dir="rtl"] .item-options-end ion-item-option:first-child {
padding-right: var(--ion-safe-area-right);
}
/* stylelint-enable property-disallowed-list */
.item-sliding-active-slide {
@include rtl() {

View File

@@ -45,6 +45,14 @@ export class ItemOptions implements ComponentInterface {
// Used internally for styling
[`item-options-${mode}`]: true,
/**
* Note: The "start" and "end" terms refer to the
* direction ion-item-option instances within ion-item-options flow.
* They do not refer to how ion-item-options flows within ion-item-sliding.
* As a result, "item-options-start" means the ion-item-options container
* always appears on the left, and "item-options-end" means the ion-item-options
* container always appears on the right.
*/
'item-options-start': !isEnd,
'item-options-end': isEnd,
}}

View File

@@ -398,7 +398,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
});
const ariaDisabled = disabled || childStyles['item-interactive-disabled'] ? 'true' : null;
const fillValue = fill || 'none';
const inList = hostContext('ion-list', this.el);
const inList = hostContext('ion-list', this.el) && !hostContext('ion-radio-group', this.el);
return (
<Host

View File

@@ -0,0 +1,51 @@
import { Radio } from '../../../radio/radio.tsx';
import { RadioGroup } from '../../../radio-group/radio-group.tsx';
import { Item } from '../../item.tsx';
import { List } from '../../../list/list.tsx';
import { newSpecPage } from '@stencil/core/testing';
describe('ion-item', () => {
it('should not have a role when used without list', async () => {
const page = await newSpecPage({
components: [Item],
html: `<ion-item>Hello World</ion-item>`,
});
const item = page.body.querySelector('ion-item');
expect(item.getAttribute('role')).toBe(null);
});
it('should have a listitem role when used inside list', async () => {
const page = await newSpecPage({
components: [Item, List],
html: `
<ion-list>
<ion-item>
Hello World
</ion-item>
</ion-list>
`,
});
const item = page.body.querySelector('ion-item');
expect(item.getAttribute('role')).toBe('listitem');
});
it('should not have a role when used inside radio group and list', async () => {
const page = await newSpecPage({
components: [Radio, RadioGroup, Item, List],
html: `
<ion-list>
<ion-radio-group value="a">
<ion-item>
<ion-radio value="other-value" aria-label="my radio"></ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
`,
});
const item = page.body.querySelector('ion-item');
expect(item.getAttribute('role')).toBe(null);
});
});

View File

@@ -180,8 +180,7 @@ export class Popover implements ComponentInterface, PopoverInterface {
/**
* Describes how to calculate the popover width.
* If `"cover"`, the popover width will match the width of the trigger.
* If `"auto"`, the popover width will be determined by the content in
* the popover.
* If `"auto"`, the popover width will be set to a static default value.
*/
@Prop() size: PopoverSize = 'auto';

View File

@@ -30,23 +30,19 @@
</ion-list-header>
<ion-item>
<ion-label>Item 1</ion-label>
<ion-radio value="1" slot="start"></ion-radio>
<ion-radio value="1">Item 1</ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 2</ion-label>
<ion-radio value="2" slot="start"></ion-radio>
<ion-radio value="2">Item 2</ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 3</ion-label>
<ion-radio value="3" slot="start"></ion-radio>
<ion-radio value="3">Item 3</ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 4</ion-label>
<ion-radio value="4" slot="start"></ion-radio>
<ion-radio value="4">Item 4</ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>

View File

@@ -1,19 +1,7 @@
import { expect } from '@playwright/test';
import type { Locator } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
import type { E2EPage } from '@utils/test/playwright';
configs().forEach(({ title, screenshot, config }) => {
test.describe(title('radio-group: basic'), () => {
test('should not have visual regressions', async ({ page }) => {
await page.goto(`/src/components/radio-group/test/basic`, config);
const list = page.locator('ion-list');
await expect(list).toHaveScreenshot(screenshot(`radio-group-diff`));
});
});
});
import { RadioFixture } from '../fixtures';
/**
* This behavior does not vary across modes/directions.
@@ -31,8 +19,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
`
<ion-radio-group value="one" allow-empty-selection="false">
<ion-item>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
<ion-radio id="one" value="one">One</ion-radio>
</ion-item>
</ion-radio-group>
`,
@@ -48,8 +35,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
`
<ion-radio-group value="one" allow-empty-selection="true">
<ion-item>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
<ion-radio id="one" value="one">One</ion-radio>
</ion-item>
</ion-radio-group>
`,
@@ -65,8 +51,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
`
<ion-radio-group value="one" allow-empty-selection="false">
<ion-item>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
<ion-radio id="one" value="one">One</ion-radio>
</ion-item>
</ion-radio-group>
`,
@@ -82,8 +67,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
`
<ion-radio-group value="one" allow-empty-selection="true">
<ion-item>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
<ion-radio id="one" value="one">One</ion-radio>
</ion-item>
</ion-radio-group>
`,
@@ -99,18 +83,15 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
`
<ion-radio-group value="1">
<ion-item>
<ion-label>Item 1</ion-label>
<ion-radio value="1" slot="start"></ion-radio>
<ion-radio value="1">Item 1</ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 2</ion-label>
<ion-radio value="2" slot="start"></ion-radio>
<ion-radio value="2">Item 2</ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 3</ion-label>
<ion-radio value="3" slot="start"></ion-radio>
<ion-radio value="3">Item 3</ion-radio>
</ion-item>
</ion-radio-group>
`,
@@ -130,34 +111,3 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
});
});
});
class RadioFixture {
readonly page: E2EPage;
private radio!: Locator;
constructor(page: E2EPage) {
this.page = page;
}
async checkRadio(method: 'keyboard' | 'mouse', selector = 'ion-radio') {
const { page } = this;
const radio = (this.radio = page.locator(selector));
if (method === 'keyboard') {
await radio.focus();
await page.keyboard.press('Space');
} else {
await radio.click();
}
await page.waitForChanges();
return radio;
}
async expectChecked(state: boolean) {
const { radio } = this;
await expect(radio.locator('input')).toHaveJSProperty('checked', state);
}
}

View File

@@ -0,0 +1,39 @@
import type { Locator } from '@playwright/test';
import { expect } from '@playwright/test';
import type { E2EPage } from '@utils/test/playwright';
export class RadioFixture {
readonly page: E2EPage;
private radio!: Locator;
constructor(page: E2EPage) {
this.page = page;
}
async checkRadio(method: 'keyboard' | 'mouse', selector = 'ion-radio') {
const { page } = this;
const radio = (this.radio = page.locator(selector));
if (method === 'keyboard') {
await radio.focus();
await page.keyboard.press('Space');
} else {
await radio.click();
}
await page.waitForChanges();
return radio;
}
async expectChecked(state: boolean) {
const { radio } = this;
if (state) {
await expect(radio).toHaveClass(/radio-checked/);
} else {
await expect(radio).not.toHaveClass(/radio-checked/);
}
}
}

View File

@@ -31,32 +31,19 @@
</ion-list-header>
<ion-item>
<ion-label
>Biff
<span id="biff"></span>
</ion-label>
<ion-radio value="biff" slot="start"></ion-radio>
<ion-radio value="biff">Biff</ion-radio>
</ion-item>
<ion-item>
<ion-label
>Griff
<span id="griff"></span>
</ion-label>
<ion-radio value="griff" slot="start"></ion-radio>
<ion-radio value="griff">Griff</ion-radio>
</ion-item>
<ion-item>
<ion-label
>Buford
<span id="buford"></span>
</ion-label>
<ion-radio value="buford" slot="start"></ion-radio>
<ion-radio value="buford">Buford</ion-radio>
</ion-item>
<ion-item>
<ion-label>George</ion-label>
<ion-radio value="george" disabled slot="start"></ion-radio>
<ion-radio value="george" disabled>George</ion-radio>
</ion-item>
<ion-item>

View File

@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Radio Group - Basic</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Radio Group - Basic</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list>
<ion-radio-group name="items" id="group" value="1">
<ion-list-header>
<ion-label>Radio Group Header</ion-label>
</ion-list-header>
<ion-item>
<ion-label>Item 1</ion-label>
<ion-radio value="1" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 2</ion-label>
<ion-radio value="2" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 3</ion-label>
<ion-radio value="3" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 4</ion-label>
<ion-radio value="4" slot="start"></ion-radio>
</ion-item>
</ion-radio-group>
</ion-list>
</ion-content>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,163 @@
import { expect } from '@playwright/test';
import type { Locator } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
import type { E2EPage } from '@utils/test/playwright';
configs().forEach(({ title, screenshot, config }) => {
test.describe(title('radio-group: basic'), () => {
test('should not have visual regressions', async ({ page }) => {
await page.goto(`/src/components/radio-group/test/legacy/basic`, config);
const list = page.locator('ion-list');
await expect(list).toHaveScreenshot(screenshot(`radio-group-diff`));
});
});
});
/**
* This behavior does not vary across modes/directions.
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('radio-group: interaction'), () => {
let radioFixture: RadioFixture;
test.beforeEach(({ page }) => {
radioFixture = new RadioFixture(page);
});
test('spacebar should not deselect without allowEmptySelection', async ({ page }) => {
await page.setContent(
`
<ion-radio-group value="one" allow-empty-selection="false">
<ion-item>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
</ion-item>
</ion-radio-group>
`,
config
);
await radioFixture.checkRadio('keyboard');
await radioFixture.expectChecked(true);
});
test('spacebar should deselect with allowEmptySelection', async ({ page }) => {
await page.setContent(
`
<ion-radio-group value="one" allow-empty-selection="true">
<ion-item>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
</ion-item>
</ion-radio-group>
`,
config
);
await radioFixture.checkRadio('keyboard');
await radioFixture.expectChecked(false);
});
test('click should not deselect without allowEmptySelection', async ({ page }) => {
await page.setContent(
`
<ion-radio-group value="one" allow-empty-selection="false">
<ion-item>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
</ion-item>
</ion-radio-group>
`,
config
);
await radioFixture.checkRadio('mouse');
await radioFixture.expectChecked(true);
});
test('click should deselect with allowEmptySelection', async ({ page }) => {
await page.setContent(
`
<ion-radio-group value="one" allow-empty-selection="true">
<ion-item>
<ion-label>One</ion-label>
<ion-radio id="one" value="one"></ion-radio>
</ion-item>
</ion-radio-group>
`,
config
);
await radioFixture.checkRadio('mouse');
await radioFixture.expectChecked(false);
});
test('programmatically assigning a value should update the checked radio', async ({ page }) => {
await page.setContent(
`
<ion-radio-group value="1">
<ion-item>
<ion-label>Item 1</ion-label>
<ion-radio value="1" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 2</ion-label>
<ion-radio value="2" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>Item 3</ion-label>
<ion-radio value="3" slot="start"></ion-radio>
</ion-item>
</ion-radio-group>
`,
config
);
const radioGroup = page.locator('ion-radio-group');
const radioOne = page.locator('ion-radio[value="1"]');
const radioTwo = page.locator('ion-radio[value="2"]');
await radioGroup.evaluate((el: HTMLIonRadioGroupElement) => (el.value = '2'));
await page.waitForChanges();
await expect(radioOne).not.toHaveClass(/radio-checked/);
await expect(radioTwo).toHaveClass(/radio-checked/);
});
});
});
class RadioFixture {
readonly page: E2EPage;
private radio!: Locator;
constructor(page: E2EPage) {
this.page = page;
}
async checkRadio(method: 'keyboard' | 'mouse', selector = 'ion-radio') {
const { page } = this;
const radio = (this.radio = page.locator(selector));
if (method === 'keyboard') {
await radio.focus();
await page.keyboard.press('Space');
} else {
await radio.click();
}
await page.waitForChanges();
return radio;
}
async expectChecked(state: boolean) {
const { radio } = this;
await expect(radio.locator('input')).toHaveJSProperty('checked', state);
}
}

View File

@@ -0,0 +1,114 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Radio Group - Form</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Radio Group - Form</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="outer-content">
<form>
<ion-list>
<ion-radio-group name="tannen" id="group" value="biff">
<ion-list-header>
<ion-label>Luckiest Man On Earth</ion-label>
</ion-list-header>
<ion-item>
<ion-label
>Biff
<span id="biff"></span>
</ion-label>
<ion-radio value="biff" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label
>Griff
<span id="griff"></span>
</ion-label>
<ion-radio value="griff" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label
>Buford
<span id="buford"></span>
</ion-label>
<ion-radio value="buford" slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-label>George</ion-label>
<ion-radio value="george" disabled slot="start"></ion-radio>
</ion-item>
<ion-item>
<ion-button type="submit">Submit</ion-button>
</ion-item>
</ion-radio-group>
</ion-list>
</form>
<p style="margin: 20px">
Value:
<span id="value"></span>
</p>
<p style="margin: 20px">
Changes:
<span id="changes">0</span>
</p>
</ion-content>
<style>
.outer-content {
--background: #f2f2f2;
}
</style>
<script>
var changes = 0;
document.getElementById('group').addEventListener('ionChange', function (ev) {
document.getElementById('value').textContent = ev.detail.value;
changes++;
document.getElementById('changes').textContent = changes;
});
var biff = 0;
document.querySelector('[value="biff"]').addEventListener('ionSelect', function (ev) {
biff++;
document.getElementById('biff').textContent = biff;
});
var griff = 0;
document.querySelector('[value="griff"]').addEventListener('ionSelect', function (ev) {
griff++;
document.getElementById('griff').textContent = griff;
});
var buford = 0;
document.querySelector('[value="buford"]').addEventListener('ionSelect', function (ev) {
buford++;
document.getElementById('buford').textContent = buford;
});
</script>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,35 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('radio-group: form'), () => {
test.beforeEach(async ({ page }) => {
await page.goto('/src/components/radio-group/test/legacy/form', config);
});
test('selecting an option should update the value', async ({ page }) => {
const radioGroup = page.locator('ion-radio-group');
const ionChange = await page.spyOnEvent('ionChange');
const griffRadio = page.locator('ion-radio[value="griff"]');
await expect(radioGroup).toHaveAttribute('value', 'biff');
await griffRadio.click();
await page.waitForChanges();
await expect(ionChange).toHaveReceivedEventDetail({ value: 'griff', event: { isTrusted: true } });
});
test('selecting a disabled option should not update the value', async ({ page }) => {
const value = page.locator('#value');
const disabledRadio = page.locator('ion-radio[value="george"]');
await expect(value).toHaveText('');
await expect(disabledRadio).toHaveAttribute('disabled', '');
await disabledRadio.click({ force: true });
await page.waitForChanges();
await expect(value).toHaveText('');
});
});
});

View File

@@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Radio Group - Search</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<ion-app>
<ion-header>
<ion-toolbar>
<ion-title>Radio Group - Form</ion-title>
</ion-toolbar>
<ion-toolbar>
<ion-searchbar placeholder="Type to filter..." debounce="0"></ion-searchbar>
</ion-toolbar>
<ion-toolbar>
<ion-note id="value">Current value:</ion-note>
</ion-toolbar>
</ion-header>
<ion-content class="outer-content">
<ion-radio-group allow-empty-selection></ion-radio-group>
</ion-content>
<style>
ion-note {
padding: 0 10px;
}
</style>
<script>
const radioGroup = document.querySelector('ion-radio-group');
const searchbar = document.querySelector('ion-searchbar');
const currentValue = document.querySelector('#value');
radioGroup.addEventListener('ionChange', (ev) => {
currentValue.innerText = `Current value: ${ev.detail.value}`;
});
searchbar.addEventListener('ionChange', (ev) => {
filter(ev.detail.value);
});
const filter = (value) => {
const query = value != null ? value.toLowerCase() : '';
const data = [
{ id: 0, value: 'zero' },
{ id: 1, value: 'one' },
{ id: 2, value: 'two' },
{ id: 3, value: 'three' },
];
let html = '';
data.forEach((d) => {
if (d.value.includes(query)) {
html += `
<ion-item>
<ion-label>Item ${d.value}</ion-label>
<ion-radio value="${d.value}"></ion-radio>
</ion-item>
`;
}
});
radioGroup.innerHTML = html;
};
filter();
</script>
</ion-app>
</body>
</html>

View File

@@ -0,0 +1,42 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
/**
* This behavior does not var across modes/directions.
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('radio-group'), () => {
test.beforeEach(async ({ page }) => {
await page.goto('/src/components/radio-group/test/legacy/search', config);
});
test.describe('radio-group: state', () => {
test('radio should remain checked after being removed/readded to the dom', async ({ page }) => {
const radioGroup = page.locator('ion-radio-group');
const radio = page.locator('ion-radio[value=two]');
const searchbarInput = page.locator('ion-searchbar input');
// select radio
await radio.click();
await expect(radio.locator('input')).toHaveJSProperty('checked', true);
// filter radio so it is not in DOM
await page.fill('ion-searchbar input', 'zero');
await searchbarInput.evaluate((el) => el.blur());
await page.waitForChanges();
await expect(radio).toBeHidden();
// ensure radio group has the same value
await expect(radioGroup).toHaveJSProperty('value', 'two');
// clear the search so the radio appears
await page.fill('ion-searchbar input', '');
await searchbarInput.evaluate((el) => el.blur());
await page.waitForChanges();
// ensure that the new radio instance is still checked
await expect(radio.locator('input')).toHaveJSProperty('checked', true);
});
});
});
});

View File

@@ -65,8 +65,7 @@
if (d.value.includes(query)) {
html += `
<ion-item>
<ion-label>Item ${d.value}</ion-label>
<ion-radio value="${d.value}"></ion-radio>
<ion-radio value="${d.value}">Item ${d.value}</ion-radio>
</ion-item>
`;
}

View File

@@ -1,42 +1,41 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
import { RadioFixture } from '../fixtures';
/**
* This behavior does not var across modes/directions.
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('radio-group'), () => {
test.beforeEach(async ({ page }) => {
test.describe(title('radio-group: search'), () => {
test('radio should remain checked after being removed/readded to the dom', async ({ page }) => {
await page.goto('/src/components/radio-group/test/search', config);
});
test.describe('radio-group: state', () => {
test('radio should remain checked after being removed/readded to the dom', async ({ page }) => {
const radioGroup = page.locator('ion-radio-group');
const radio = page.locator('ion-radio[value=two]');
const searchbarInput = page.locator('ion-searchbar input');
const radioFixture = new RadioFixture(page);
// select radio
await radio.click();
await expect(radio.locator('input')).toHaveJSProperty('checked', true);
const radioGroup = page.locator('ion-radio-group');
const searchbarInput = page.locator('ion-searchbar input');
// filter radio so it is not in DOM
await page.fill('ion-searchbar input', 'zero');
await searchbarInput.evaluate((el) => el.blur());
await page.waitForChanges();
await expect(radio).toBeHidden();
// select radio
const radio = await radioFixture.checkRadio('mouse', 'ion-radio[value=two]');
await radioFixture.expectChecked(true);
// ensure radio group has the same value
await expect(radioGroup).toHaveJSProperty('value', 'two');
// filter radio so it is not in DOM
await page.fill('ion-searchbar input', 'zero');
await searchbarInput.evaluate((el) => el.blur());
await page.waitForChanges();
await expect(radio).toBeHidden();
// clear the search so the radio appears
await page.fill('ion-searchbar input', '');
await searchbarInput.evaluate((el) => el.blur());
await page.waitForChanges();
// ensure radio group has the same value
await expect(radioGroup).toHaveJSProperty('value', 'two');
// ensure that the new radio instance is still checked
await expect(radio.locator('input')).toHaveJSProperty('checked', true);
});
// clear the search so the radio appears
await page.fill('ion-searchbar input', '');
await searchbarInput.evaluate((el) => el.blur());
await page.waitForChanges();
// ensure that the new radio instance is still checked
await radioFixture.expectChecked(true);
});
});
});

View File

@@ -2,8 +2,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core';
import type { LegacyFormController } from '@utils/forms';
import { createLegacyFormController } from '@utils/forms';
import type { Attributes } from '@utils/helpers';
import { addEventListener, getAriaLabel, inheritAriaAttributes, removeEventListener } from '@utils/helpers';
import { addEventListener, getAriaLabel, removeEventListener } from '@utils/helpers';
import { printIonWarning } from '@utils/logging';
import { createColorClasses, hostContext } from '@utils/theme';
@@ -31,7 +30,6 @@ export class Radio implements ComponentInterface {
private radioGroup: HTMLIonRadioGroupElement | null = null;
private nativeInput!: HTMLInputElement;
private legacyFormController!: LegacyFormController;
private inheritedAttributes: Attributes = {};
// This flag ensures we log the deprecation warning at most once.
private hasLoggedDeprecationWarning = false;
@@ -135,8 +133,7 @@ export class Radio implements ComponentInterface {
ev.stopPropagation();
ev.preventDefault();
const element = this.legacyFormController.hasLegacyControl() ? this.el : this.nativeInput;
element.focus();
this.el.focus();
}
/** @internal */
@@ -167,12 +164,6 @@ export class Radio implements ComponentInterface {
componentWillLoad() {
this.emitStyle();
if (!this.legacyFormController.hasLegacyControl()) {
this.inheritedAttributes = {
...inheritAriaAttributes(this.el),
};
}
}
@Watch('checked')
@@ -201,7 +192,34 @@ export class Radio implements ComponentInterface {
};
private onClick = () => {
this.checked = this.nativeInput.checked;
const { radioGroup, checked } = this;
/**
* The legacy control uses a native input inside
* of the radio host, so we can set this.checked
* to the state of the nativeInput. RadioGroup
* will prevent the native input from checking if
* allowEmptySelection="false" by calling ev.preventDefault().
*/
if (this.legacyFormController.hasLegacyControl()) {
this.checked = this.nativeInput.checked;
return;
}
/**
* The modern control does not use a native input
* inside of the radio host, so we cannot rely on the
* ev.preventDefault() behavior above. If the radio
* is checked and the parent radio group allows for empty
* selection, then we can set the checked state to false.
* Otherwise, the checked state should always be set
* to true because the checked state cannot be toggled.
*/
if (checked && radioGroup?.allowEmptySelection) {
this.checked = false;
} else {
this.checked = true;
}
};
private onFocus = () => {
@@ -232,23 +250,14 @@ export class Radio implements ComponentInterface {
}
private renderRadio() {
const {
checked,
disabled,
inputId,
color,
el,
justify,
labelPlacement,
inheritedAttributes,
hasLabel,
buttonTabindex,
} = this;
const { checked, disabled, color, el, justify, labelPlacement, hasLabel, buttonTabindex } = this;
const mode = getIonMode(this);
const inItem = hostContext('ion-item', el);
return (
<Host
onFocus={this.onFocus}
onBlur={this.onBlur}
onClick={this.onClick}
class={createColorClasses(color, {
[mode]: true,
@@ -261,21 +270,12 @@ export class Radio implements ComponentInterface {
'ion-activatable': !inItem,
'ion-focusable': !inItem,
})}
role="radio"
aria-checked={checked ? 'true' : 'false'}
aria-disabled={disabled ? 'true' : null}
tabindex={buttonTabindex}
>
<label class="radio-wrapper">
{/*
The native control must be rendered
before the visible label text due to https://bugs.webkit.org/show_bug.cgi?id=251951
*/}
<input
type="radio"
checked={checked}
disabled={disabled}
id={inputId}
tabindex={buttonTabindex}
ref={(nativeEl) => (this.nativeInput = nativeEl as HTMLInputElement)}
{...inheritedAttributes}
/>
<div
class={{
'label-text-wrapper': true,

View File

@@ -16,10 +16,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
});
test.describe(title('radio: keyboard navigation'), () => {
test.beforeEach(async ({ page, skip }) => {
// TODO (FW-2979)
skip.browser('webkit', 'Safari 16 only allows text fields and pop-up menus to be focused.');
test.beforeEach(async ({ page }) => {
await page.setContent(
`
<ion-app>

View File

@@ -95,7 +95,7 @@ configs().forEach(({ title, screenshot, config }) => {
test('should apply color correctly', async ({ page }) => {
await page.setContent(
`
<ion-radio slot="start" value="pepperoni" color="success"></ion-radio>
<ion-radio legacy="true" value="pepperoni" color="success"></ion-radio>
`,
config
);
@@ -138,7 +138,7 @@ configs({ directions: ['ltr'] }).forEach(({ title, config }) => {
test('radio should be checked when activated even without a radio group', async ({ page }) => {
await page.setContent(
`
<ion-radio slot="start" value="pepperoni"></ion-radio>
<ion-radio legacy="true" value="pepperoni"></ion-radio>
`,
config
);

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/docs
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)
**Note:** Version bump only for package @ionic/docs

View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/docs",
"version": "7.2.0",
"version": "7.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/docs",
"version": "7.2.0",
"version": "7.2.1",
"license": "MIT"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/docs",
"version": "7.2.0",
"version": "7.2.1",
"description": "Pre-packaged API documentation for the Ionic docs.",
"main": "core.json",
"types": "core.d.ts",

View File

@@ -4,5 +4,5 @@
"docs",
"packages/*"
],
"version": "7.2.0"
"version": "7.2.1"
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/angular-server
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)
**Note:** Version bump only for package @ionic/angular-server

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular-server",
"version": "7.2.0",
"version": "7.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular-server",
"version": "7.2.0",
"version": "7.2.1",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.2.0"
"@ionic/core": "^7.2.1"
},
"devDependencies": {
"@angular-eslint/eslint-plugin": "^14.0.0",
@@ -1060,9 +1060,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -7342,9 +7342,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular-server",
"version": "7.2.0",
"version": "7.2.1",
"description": "Angular SSR Module for Ionic",
"keywords": [
"ionic",
@@ -61,6 +61,6 @@
},
"prettier": "@ionic/prettier-config",
"dependencies": {
"@ionic/core": "^7.2.0"
"@ionic/core": "^7.2.1"
}
}

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/angular
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular",
"version": "7.2.0",
"version": "7.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular",
"version": "7.2.0",
"version": "7.2.1",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.2.0",
"@ionic/core": "^7.2.1",
"ionicons": "^7.0.0",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"
@@ -1227,9 +1227,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -8104,9 +8104,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "7.2.0",
"version": "7.2.1",
"description": "Angular specific wrappers for @ionic/core",
"keywords": [
"ionic",
@@ -47,7 +47,7 @@
}
},
"dependencies": {
"@ionic/core": "^7.2.0",
"@ionic/core": "^7.2.1",
"ionicons": "^7.0.0",
"jsonc-parser": "^3.0.0",
"tslib": "^2.3.0"

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/react-router
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)
**Note:** Version bump only for package @ionic/react-router

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/react-router",
"version": "7.2.0",
"version": "7.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/react-router",
"version": "7.2.0",
"version": "7.2.1",
"license": "MIT",
"dependencies": {
"@ionic/react": "^7.2.0",
"@ionic/react": "^7.2.1",
"tslib": "*"
},
"devDependencies": {
@@ -205,9 +205,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -401,11 +401,11 @@
}
},
"node_modules/@ionic/react": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.2.0.tgz",
"integrity": "sha512-90evXKqkpptvSVYbxyyY/D9hE20ZbRV1dyZhxL5PzeLt4eIuvz6G11gTfH8YCyEAhBFZpCsg+mpcIcoP45ITvA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.2.1.tgz",
"integrity": "sha512-z9PSPoBiHsdwotdywGcnln1y01wjLoS09vmCUboc+2qSadVMczcjEuBxfRIvjVbaQt3u8RGHif2+K3AgGNKMeA==",
"dependencies": {
"@ionic/core": "7.2.0",
"@ionic/core": "7.2.1",
"ionicons": "^7.0.0",
"tslib": "*"
},
@@ -486,9 +486,9 @@
}
},
"node_modules/@stencil/core": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.1.tgz",
"integrity": "sha512-7rjOmM0W9K5op2gtOQRLERGH1155rv2fm6ppxOzYqqG8ISct4m9skp5XgUBYPu+GSPsJFdRuCIQs0IuVsG/7+g==",
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.2.tgz",
"integrity": "sha512-FAUhUVaakCy29nU2GwO/HQBRV1ihPRvncz3PUc8oR+UJLAxGabTmP8PLY7wvHfbw+Cvi4VXfJFTBvdfDu6iKPQ==",
"bin": {
"stencil": "bin/stencil"
},
@@ -3663,9 +3663,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -3786,11 +3786,11 @@
"requires": {}
},
"@ionic/react": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.2.0.tgz",
"integrity": "sha512-90evXKqkpptvSVYbxyyY/D9hE20ZbRV1dyZhxL5PzeLt4eIuvz6G11gTfH8YCyEAhBFZpCsg+mpcIcoP45ITvA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.2.1.tgz",
"integrity": "sha512-z9PSPoBiHsdwotdywGcnln1y01wjLoS09vmCUboc+2qSadVMczcjEuBxfRIvjVbaQt3u8RGHif2+K3AgGNKMeA==",
"requires": {
"@ionic/core": "7.2.0",
"@ionic/core": "7.2.1",
"ionicons": "^7.0.0",
"tslib": "*"
}
@@ -3844,9 +3844,9 @@
}
},
"@stencil/core": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.1.tgz",
"integrity": "sha512-7rjOmM0W9K5op2gtOQRLERGH1155rv2fm6ppxOzYqqG8ISct4m9skp5XgUBYPu+GSPsJFdRuCIQs0IuVsG/7+g=="
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.2.tgz",
"integrity": "sha512-FAUhUVaakCy29nU2GwO/HQBRV1ihPRvncz3PUc8oR+UJLAxGabTmP8PLY7wvHfbw+Cvi4VXfJFTBvdfDu6iKPQ=="
},
"@types/estree": {
"version": "0.0.39",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react-router",
"version": "7.2.0",
"version": "7.2.1",
"description": "React Router wrapper for @ionic/react",
"keywords": [
"ionic",
@@ -37,7 +37,7 @@
"dist/"
],
"dependencies": {
"@ionic/react": "^7.2.0",
"@ionic/react": "^7.2.1",
"tslib": "*"
},
"peerDependencies": {

View File

@@ -270,13 +270,13 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
const goBack = history.goBack || history.back;
goBack();
} else {
this.handleNavigate(prevInfo.pathname + (prevInfo.search || ''), 'pop', 'back');
this.handleNavigate(prevInfo.pathname + (prevInfo.search || ''), 'pop', 'back', routeAnimation);
}
} else {
this.handleNavigate(defaultHref as string, 'pop', 'back');
this.handleNavigate(defaultHref as string, 'pop', 'back', routeAnimation);
}
} else {
this.handleNavigate(defaultHref as string, 'pop', 'back');
this.handleNavigate(defaultHref as string, 'pop', 'back', routeAnimation);
}
}

View File

@@ -1,6 +1,7 @@
import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react';
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
import React from 'react';
import type { PropsWithChildren } from 'react';
import React, { cloneElement, useContext, useEffect, useMemo, useReducer, useRef, useState } from 'react';
import { matchPath } from 'react-router-dom';
import { clonePageElement } from './clonePageElement';
@@ -11,76 +12,93 @@ interface StackManagerProps {
routeInfo: RouteInfo;
}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface StackManagerState {}
const isViewVisible = (el: HTMLElement) =>
!el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden');
export class StackManager extends React.PureComponent<StackManagerProps, StackManagerState> {
id: string;
context!: React.ContextType<typeof RouteManagerContext>;
ionRouterOutlet?: React.ReactElement;
routerOutletElement: HTMLIonRouterOutletElement | undefined;
prevProps?: StackManagerProps;
skipTransition: boolean;
export const StackManager = ({ children, ...props }: PropsWithChildren<StackManagerProps>) => {
const { routeInfo } = props;
const {
findViewItemByRouteInfo,
findLeavingViewItemByRouteInfo,
findViewItemByPathname,
createViewItem,
addViewItem,
goBack,
getChildrenToRender,
clearOutlet,
} = useContext(RouteManagerContext);
stackContextValue: StackContextState = {
registerIonPage: this.registerIonPage.bind(this),
isInOutlet: () => true,
};
const routerOutletRef = useRef<HTMLIonRouterOutletElement>();
const ionRouterOutletRef = useRef<React.ReactElement>();
const skipTransitionRef = useRef(false);
const clearOutletTimeout = useRef(null);
const pendingPageTransitionRef = useRef(false);
const prevProps = useRef<{ routeInfo: RouteInfo }>();
private clearOutletTimeout: any;
private pendingPageTransition = false;
const forceUpdate = useReducer((x) => x + 1, 0)[1];
constructor(props: StackManagerProps) {
super(props);
this.registerIonPage = this.registerIonPage.bind(this);
this.transitionPage = this.transitionPage.bind(this);
this.handlePageTransition = this.handlePageTransition.bind(this);
this.id = generateId('routerOutlet');
this.prevProps = undefined;
this.skipTransition = false;
}
const [id] = useState(generateId('routerOutlet'));
componentDidMount() {
if (this.clearOutletTimeout) {
/**
* The clearOutlet integration with React Router is a bit hacky.
* It uses a timeout to clear the outlet after a transition.
* In React v18, components are mounted and unmounted in development mode
* to check for side effects.
*
* This clearTimeout prevents the outlet from being cleared when the component is re-mounted,
* which should only happen in development mode and as a result of a hot reload.
*/
clearTimeout(this.clearOutletTimeout);
const stackContextValue: StackContextState = useMemo(
() => ({
isInOutlet: () => true,
registerIonPage: (page: HTMLElement, routeInfo: RouteInfo) => {
const foundView = findViewItemByRouteInfo(routeInfo, id);
if (foundView) {
const oldPageElement = foundView.ionPageElement;
foundView.ionPageElement = page;
foundView.ionRoute = true;
/**
* React 18 will unmount and remount IonPage
* elements in development mode when using createRoot.
* This can cause duplicate page transitions to occur.
*/
if (oldPageElement === page) {
return;
}
}
handlePageTransition(routeInfo);
},
}),
[routerOutletRef.current]
);
useEffect(() => {
const routerOutletElement = routerOutletRef.current;
if (routerOutletElement) {
// Mount behavior for the initial route
setupRouterOutlet(routerOutletElement);
handlePageTransition(routeInfo);
}
if (this.routerOutletElement) {
this.setupRouterOutlet(this.routerOutletElement);
this.handlePageTransition(this.props.routeInfo);
}, []);
useEffect(() => {
const { pathname } = routeInfo;
if (pathname !== prevProps.current?.routeInfo.pathname) {
prevProps.current = props;
handlePageTransition(routeInfo);
} else if (pendingPageTransitionRef.current) {
handlePageTransition(routeInfo);
pendingPageTransitionRef.current = false;
}
}
}, [routeInfo]);
componentDidUpdate(prevProps: StackManagerProps) {
const { pathname } = this.props.routeInfo;
const { pathname: prevPathname } = prevProps.routeInfo;
useEffect(() => {
return () => {
clearOutlet(id);
if (clearOutletTimeout.current) {
clearTimeout(clearOutletTimeout.current);
clearOutletTimeout.current = null;
}
};
}, []);
if (pathname !== prevPathname) {
this.prevProps = prevProps;
this.handlePageTransition(this.props.routeInfo);
} else if (this.pendingPageTransition) {
this.handlePageTransition(this.props.routeInfo);
this.pendingPageTransition = false;
}
}
componentWillUnmount() {
this.clearOutletTimeout = this.context.clearOutlet(this.id);
}
async handlePageTransition(routeInfo: RouteInfo) {
if (!this.routerOutletElement || !this.routerOutletElement.commit) {
const handlePageTransition = (routeInfo: RouteInfo) => {
const routerOutletElement = routerOutletRef.current;
if (!routerOutletElement || !routerOutletElement.commit) {
/**
* The route outlet has not mounted yet. We need to wait for it to render
* before we can transition the page.
@@ -88,142 +106,126 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
* Set a flag to indicate that we should transition the page after
* the component has updated.
*/
this.pendingPageTransition = true;
} else {
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
pendingPageTransitionRef.current = true;
return;
}
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
}
let enteringViewItem = findViewItemByRouteInfo(routeInfo, id);
let leavingViewItem = findLeavingViewItemByRouteInfo(routeInfo, id);
// Check if leavingViewItem should be unmounted
if (leavingViewItem) {
if (routeInfo.routeAction === 'replace') {
leavingViewItem.mount = false;
} else if (!(routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward')) {
if (routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
leavingViewItem.mount = false;
}
} else if (routeInfo.routeOptions?.unmount) {
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
leavingViewItem = findViewItemByPathname(routeInfo.prevRouteLastPathname, id);
}
// Check if the leavingViewItem should be unmounted
if (leavingViewItem) {
if (routeInfo.routeAction === 'replace') {
leavingViewItem.mount = false;
} else if (!(routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward')) {
if (routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
leavingViewItem.mount = false;
}
} else if (routeInfo.routeOptions?.unmount) {
leavingViewItem.mount = false;
}
}
const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
const enteringRoute = matchRoute(ionRouterOutletRef.current?.props.children, routeInfo) as React.ReactElement;
if (enteringViewItem) {
enteringViewItem.reactElement = enteringRoute;
} else if (enteringRoute) {
enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo);
this.context.addViewItem(enteringViewItem);
}
if (enteringViewItem) {
// If the entering view is already in the stack, then we need to clone it
enteringViewItem.reactElement = enteringRoute;
} else if (enteringRoute) {
// Otherwise we need to create a new view item
enteringViewItem = createViewItem(id, enteringRoute, routeInfo);
addViewItem(enteringViewItem);
}
if (enteringViewItem && enteringViewItem.ionPageElement) {
if (enteringViewItem && enteringViewItem.ionPageElement) {
/**
* If the entering view item is the same as the leaving view item,
* then we don't need to transition.
*/
if (enteringViewItem === leavingViewItem) {
/**
* If the entering view item is the same as the leaving view item,
* then we don't need to transition.
* we are either transitioning using parameterized routes to the same view
* or a parent router outlet is re-rendering as a result of React props changing.
*
* If the route data does not match the current path, the parent router outlet
* is attempting to transition and we cancel the operation.
*/
if (enteringViewItem === leavingViewItem) {
/**
* If the entering view item is the same as the leaving view item,
* we are either transitioning using parameterized routes to the same view
* or a parent router outlet is re-rendering as a result of React props changing.
*
* If the route data does not match the current path, the parent router outlet
* is attempting to transition and we cancel the operation.
*/
if (enteringViewItem.routeData.match.url !== routeInfo.pathname) {
return;
}
}
/**
* If there isn't a leaving view item, but the route info indicates
* that the user has routed from a previous path, then we need
* to find the leaving view item to transition between.
*/
if (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
}
/**
* If the entering view is already visible and the leaving view is not, the transition does not need to occur.
*/
if (
isViewVisible(enteringViewItem.ionPageElement) &&
leavingViewItem !== undefined &&
!isViewVisible(leavingViewItem.ionPageElement!)
) {
if (enteringViewItem.routeData.match.url !== routeInfo.pathname) {
return;
}
/**
* The view should only be transitioned in the following cases:
* 1. Performing a replace or pop action, such as a swipe to go back gesture
* to animation the leaving view off the screen.
*
* 2. Navigating between top-level router outlets, such as /page-1 to /page-2;
* or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2.
*
* 3. The entering view is an ion-router-outlet containing a page
* matching the current route and that hasn't already transitioned in.
*
* This should only happen when navigating directly to a nested router outlet
* route or on an initial page load (i.e. refreshing). In cases when loading
* /tabs/tab-1, we need to transition the /tabs page element into the view.
*/
this.transitionPage(routeInfo, enteringViewItem, leavingViewItem);
} else if (leavingViewItem && !enteringRoute && !enteringViewItem) {
// If we have a leavingView but no entering view/route, we are probably leaving to
// another outlet, so hide this leavingView. We do it in a timeout to give time for a
// transition to finish.
// setTimeout(() => {
if (leavingViewItem.ionPageElement) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
}
// }, 250);
}
this.forceUpdate();
}
}
registerIonPage(page: HTMLElement, routeInfo: RouteInfo) {
const foundView = this.context.findViewItemByRouteInfo(routeInfo, this.id);
if (foundView) {
const oldPageElement = foundView.ionPageElement;
foundView.ionPageElement = page;
foundView.ionRoute = true;
/**
* React 18 will unmount and remount IonPage
* elements in development mode when using createRoot.
* This can cause duplicate page transitions to occur.
* If there isn't a leaving view item, but the route info indicates
* that the user has routed from a previous path, then we need
* to find the leaving view item to transition between.
*/
if (oldPageElement === page) {
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
leavingViewItem = findViewItemByPathname(routeInfo.prevRouteLastPathname, id);
}
/**
* If the entering view is already visible and the leaving view is not, the transition does not need to occur.
*/
if (
isViewVisible(enteringViewItem.ionPageElement) &&
leavingViewItem !== undefined &&
!isViewVisible(leavingViewItem.ionPageElement!)
) {
return;
}
}
this.handlePageTransition(routeInfo);
}
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
const canStart = () => {
/**
* The view should only be transitioned in the following cases:
* 1. Performing a replace or pop action, such as a swipe to go back gesture
* to animation the leaving view off the screen.
*
* 2. Navigating between top-level router outlets, such as /page-1 to /page-2;
* or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2.
*
* 3. The entering view is an ion-router-outlet containing a page
* matching the current route and that hasn't already transitioned in.
*
* This should only happen when navigating directly to a nested router outlet
* route or on an initial page load (i.e. refreshing). In cases when loading
* /tabs/tab-1, we need to transition the /tabs page element into the view.
*/
transitionPage(routeInfo, enteringViewItem, leavingViewItem!);
} else if (leavingViewItem && !enteringRoute && !enteringViewItem) {
// If we have a leavingView but no entering view/route, we are probably leaving to
// another outlet, so hide this leavingView.
if (leavingViewItem.ionPageElement) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
}
}
// This causes the router outlet to re-render with the updated view items.
// Without it, a push navigation will remove the previous route's view item,
// but will not render the new route's view item.
forceUpdate();
};
const setupRouterOutlet = (routerOutlet: HTMLIonRouterOutletElement) => {
const canStart = (): boolean => {
const config = getConfig();
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
const swipeEnabled = config?.getBoolean('swipeBackEnabled', routerOutlet.mode === 'ios');
if (!swipeEnabled) {
return false;
}
const { routeInfo } = this.props;
const propsToUse =
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? prevProps.current!.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
return (
!!enteringViewItem &&
@@ -247,14 +249,12 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
};
const onStart = async () => {
const { routeInfo } = this.props;
const propsToUse =
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? prevProps.current!.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
const leavingViewItem = findViewItemByRouteInfo(routeInfo, id, false);
/**
* When the gesture starts, kick off
@@ -262,30 +262,28 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
* via a swipe gesture.
*/
if (enteringViewItem && leavingViewItem) {
await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
await transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
}
return Promise.resolve();
};
const onEnd = (shouldContinue: boolean) => {
if (shouldContinue) {
this.skipTransition = true;
this.context.goBack();
skipTransitionRef.current = true;
goBack();
} else {
/**
* In the event that the swipe
* gesture was aborted, we should
* re-hide the page that was going to enter.
*/
const { routeInfo } = this.props;
const propsToUse =
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? prevProps.current!.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
const leavingViewItem = findViewItemByRouteInfo(routeInfo, id, false);
/**
* Ionic React has a design defect where it
@@ -309,17 +307,17 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
onStart,
onEnd,
};
}
};
async transitionPage(
const transitionPage = async (
routeInfo: RouteInfo,
enteringViewItem: ViewItem,
leavingViewItem?: ViewItem,
leavingViewItem: ViewItem,
direction?: 'forward' | 'back',
progressAnimation = false
) {
) => {
const runCommit = async (enteringEl: HTMLElement, leavingEl?: HTMLElement) => {
const skipTransition = this.skipTransition;
const skipTransition = skipTransitionRef.current;
/**
* If the transition was handled
@@ -342,102 +340,99 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
* transition triggered by handlePageTransition
* in componentDidUpdate.
*/
this.skipTransition = false;
skipTransitionRef.current = false;
} else {
enteringEl.classList.add('ion-page');
enteringEl.classList.add('ion-page-invisible');
enteringEl.classList.add('ion-page', 'ion-page-invisible');
}
await routerOutlet.commit(enteringEl, leavingEl, {
duration: skipTransition || directionToUse === undefined ? 0 : undefined,
direction: directionToUse,
showGoBack: !!routeInfo.pushedByRoute,
progressAnimation,
animationBuilder: routeInfo.routeAnimation,
});
const routerOutletElement = routerOutletRef.current;
if (routerOutletElement) {
await routerOutletElement.commit(enteringEl, leavingEl, {
duration: skipTransitionRef.current || directionToUse === undefined ? 0 : undefined,
direction: directionToUse,
showGoBack: !!routeInfo.pushedByRoute,
progressAnimation,
animationBuilder: routeInfo.routeAnimation,
});
}
};
const routerOutlet = this.routerOutletElement!;
const routeInfoFallbackDirection =
const routerInfoFallbackDirection =
routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection;
const directionToUse = direction ?? routeInfoFallbackDirection;
const directionToUse = direction ?? routerInfoFallbackDirection;
if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) {
if (leavingViewItem && leavingViewItem.ionPageElement && enteringViewItem === leavingViewItem) {
const routerOutletElement = routerOutletRef.current;
if (enteringViewItem?.ionPageElement && routerOutletElement) {
if (leavingViewItem?.ionPageElement && enteringViewItem === leavingViewItem) {
// If a page is transitioning to another version of itself
// we clone it so we can have an animation to show
const match = matchComponent(leavingViewItem.reactElement, routeInfo.pathname, true);
if (match) {
const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML);
if (newLeavingElement) {
this.routerOutletElement.appendChild(newLeavingElement);
routerOutletElement.appendChild(newLeavingElement);
await runCommit(enteringViewItem.ionPageElement, newLeavingElement);
this.routerOutletElement.removeChild(newLeavingElement);
routerOutletElement.removeChild(newLeavingElement);
}
} else {
await runCommit(enteringViewItem.ionPageElement, undefined);
}
} else {
await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
if (leavingViewItem?.ionPageElement && !progressAnimation) {
const { ionPageElement } = leavingViewItem;
ionPageElement.setAttribute('aria-hidden', 'true');
ionPageElement.classList.add('ion-page-hidden');
}
}
}
}
};
render() {
const { children } = this.props;
const ionRouterOutlet = React.Children.only(children) as React.ReactElement;
this.ionRouterOutlet = ionRouterOutlet;
const renderComponents = () => {
const ionRouterOutlet = React.Children.only<React.ReactElement>(children as React.ReactElement);
ionRouterOutletRef.current = ionRouterOutlet;
const components = this.context.getChildrenToRender(this.id, this.ionRouterOutlet, this.props.routeInfo, () => {
this.forceUpdate();
const components = getChildrenToRender(id, ionRouterOutlet, routeInfo, () => {
forceUpdate();
});
return (
<StackContext.Provider value={this.stackContextValue}>
{React.cloneElement(
ionRouterOutlet as any,
{
ref: (node: HTMLIonRouterOutletElement) => {
if (ionRouterOutlet.props.setRef) {
ionRouterOutlet.props.setRef(node);
}
if (ionRouterOutlet.props.forwardedRef) {
ionRouterOutlet.props.forwardedRef.current = node;
}
this.routerOutletElement = node;
const { ref } = ionRouterOutlet as any;
if (typeof ref === 'function') {
ref(node);
}
},
},
components
)}
</StackContext.Provider>
);
}
return cloneElement(ionRouterOutlet, {
ref: (node: HTMLIonRouterOutletElement) => {
if (ionRouterOutlet.props.setRef) {
ionRouterOutlet.props.setRef(node);
}
if (ionRouterOutlet.props.forwardedRef) {
ionRouterOutlet.props.forwardedRef.current = node;
}
static get contextType() {
return RouteManagerContext;
}
}
routerOutletRef.current = node;
const { ref } = ionRouterOutlet as any;
if (typeof ref === 'function') {
ref(node);
}
},
components,
});
};
return <StackContext.Provider value={stackContextValue}>{renderComponents()}</StackContext.Provider>;
};
export default StackManager;
function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) {
let matchedNode: React.ReactNode;
React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => {
const matchProps = {
exact: child.props.exact,
path: child.props.path || child.props.from,
component: child.props.component,
};
// In React Router v6 the prop arguments are in a different order
const match = matchPath(routeInfo.pathname, matchProps);
if (match) {
matchedNode = child;
@@ -464,6 +459,7 @@ function matchComponent(node: React.ReactElement, pathname: string, forceExact?:
path: node.props.path || node.props.from,
component: node.props.component,
};
// In React Router v6 the prop arguments are in a different order
const match = matchPath(pathname, matchProps);
return match;

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/react
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)
**Note:** Version bump only for package @ionic/react

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/react",
"version": "7.2.0",
"version": "7.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/react",
"version": "7.2.0",
"version": "7.2.1",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.2.0",
"@ionic/core": "^7.2.1",
"ionicons": "^7.0.0",
"tslib": "*"
},
@@ -697,9 +697,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -11778,9 +11778,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/react",
"version": "7.2.0",
"version": "7.2.1",
"description": "React specific wrapper for @ionic/core",
"keywords": [
"ionic",
@@ -41,7 +41,7 @@
"css/"
],
"dependencies": {
"@ionic/core": "^7.2.0",
"@ionic/core": "^7.2.1",
"ionicons": "^7.0.0",
"tslib": "*"
},

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/vue-router
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)
**Note:** Version bump only for package @ionic/vue-router

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/vue-router",
"version": "7.2.0",
"version": "7.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue-router",
"version": "7.2.0",
"version": "7.2.1",
"license": "MIT",
"dependencies": {
"@ionic/vue": "^7.2.0"
"@ionic/vue": "^7.2.1"
},
"devDependencies": {
"@ionic/eslint-config": "^0.3.0",
@@ -660,9 +660,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -871,11 +871,11 @@
}
},
"node_modules/@ionic/vue": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.2.0.tgz",
"integrity": "sha512-KcfQ00pwevAhaRh5izlKnOEt9y38896WB6Qp8q8wpLux/+Ax4o+XPWNkSt5NxtEPHesdop5qv/oFTENn2diKnw==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.2.1.tgz",
"integrity": "sha512-l9ucLk1NrAZHXdAT8qrJYArklS7cCok6/Qlq52Kg0+C912sfV3IIEMxjfQw8e6MX0aZGkSYMG/BGLEBqxp6gOw==",
"dependencies": {
"@ionic/core": "7.2.0",
"@ionic/core": "7.2.1",
"ionicons": "^7.0.0"
}
},
@@ -1323,9 +1323,9 @@
}
},
"node_modules/@stencil/core": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.1.tgz",
"integrity": "sha512-7rjOmM0W9K5op2gtOQRLERGH1155rv2fm6ppxOzYqqG8ISct4m9skp5XgUBYPu+GSPsJFdRuCIQs0IuVsG/7+g==",
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.2.tgz",
"integrity": "sha512-FAUhUVaakCy29nU2GwO/HQBRV1ihPRvncz3PUc8oR+UJLAxGabTmP8PLY7wvHfbw+Cvi4VXfJFTBvdfDu6iKPQ==",
"bin": {
"stencil": "bin/stencil"
},
@@ -6807,9 +6807,9 @@
}
},
"node_modules/tslib": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
"integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA=="
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
"integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig=="
},
"node_modules/tsutils": {
"version": "3.21.0",
@@ -7697,9 +7697,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -7829,11 +7829,11 @@
"requires": {}
},
"@ionic/vue": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.2.0.tgz",
"integrity": "sha512-KcfQ00pwevAhaRh5izlKnOEt9y38896WB6Qp8q8wpLux/+Ax4o+XPWNkSt5NxtEPHesdop5qv/oFTENn2diKnw==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.2.1.tgz",
"integrity": "sha512-l9ucLk1NrAZHXdAT8qrJYArklS7cCok6/Qlq52Kg0+C912sfV3IIEMxjfQw8e6MX0aZGkSYMG/BGLEBqxp6gOw==",
"requires": {
"@ionic/core": "7.2.0",
"@ionic/core": "7.2.1",
"ionicons": "^7.0.0"
}
},
@@ -8192,9 +8192,9 @@
}
},
"@stencil/core": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.1.tgz",
"integrity": "sha512-7rjOmM0W9K5op2gtOQRLERGH1155rv2fm6ppxOzYqqG8ISct4m9skp5XgUBYPu+GSPsJFdRuCIQs0IuVsG/7+g=="
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-3.4.2.tgz",
"integrity": "sha512-FAUhUVaakCy29nU2GwO/HQBRV1ihPRvncz3PUc8oR+UJLAxGabTmP8PLY7wvHfbw+Cvi4VXfJFTBvdfDu6iKPQ=="
},
"@tootallnate/once": {
"version": "2.0.0",
@@ -12220,9 +12220,9 @@
}
},
"tslib": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
"integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA=="
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz",
"integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig=="
},
"tsutils": {
"version": "3.21.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue-router",
"version": "7.2.0",
"version": "7.2.1",
"description": "Vue Router integration for @ionic/vue",
"scripts": {
"test.spec": "jest",
@@ -45,7 +45,7 @@
},
"homepage": "https://github.com/ionic-team/ionic#readme",
"dependencies": {
"@ionic/vue": "^7.2.0"
"@ionic/vue": "^7.2.1"
},
"devDependencies": {
"@ionic/eslint-config": "^0.3.0",

View File

@@ -186,10 +186,10 @@ export const createIonRouter = (
router.go(prevInfo.position - routeInfo.position);
}
} else {
handleNavigate(defaultHref, "pop", "back");
handleNavigate(defaultHref, "pop", "back", routerAnimation);
}
} else {
handleNavigate(defaultHref, "pop", "back");
handleNavigate(defaultHref, "pop", "back", routerAnimation);
}
};

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)
**Note:** Version bump only for package @ionic/vue
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/vue",
"version": "7.2.0",
"version": "7.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue",
"version": "7.2.0",
"version": "7.2.1",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.2.0",
"@ionic/core": "^7.2.1",
"ionicons": "^7.0.0"
},
"devDependencies": {
@@ -207,9 +207,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -3746,9 +3746,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.0.tgz",
"integrity": "sha512-FV2EAppqKc8jSedEOLZe25eJeW+Pqds2fFjyrHzlKododA1rxoFmiszBQC+e5cvrL1AEFSl/y0VxU/LNpImSZA==",
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue",
"version": "7.2.0",
"version": "7.2.1",
"description": "Vue specific wrapper for @ionic/core",
"scripts": {
"eslint": "eslint src",
@@ -66,7 +66,7 @@
"vue-router": "^4.0.16"
},
"dependencies": {
"@ionic/core": "^7.2.0",
"@ionic/core": "^7.2.1",
"ionicons": "^7.0.0"
},
"vetur": {