Compare commits

..

16 Commits

Author SHA1 Message Date
ionitron
0cb37430d3 v7.2.3 2023-08-09 16:27:57 +00:00
Liam DeBeasi
e9fa30002b fix(button): hidden button is added when form is set async (#27955)
Issue number: resolves #27952

---------

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

The hidden button in `ion-button` that is responsible for submitting the
form does not get added when the `form` property is set async.

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

- `ion-button` now checks to see if it needs to render a hidden button
whenever it re-renders. This allows it to account for changes to the
`type` property, `form` property, etc.

Since this code can potentially run multiple times I added an extra
check so we don't add multiple buttons to the form.

## 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.3-dev.11691523847.1760ab58`
2023-08-09 14:35:11 +00:00
Amanda Johnston
07dee74714 refactor(datetime): remove unused isPresented variable (#27956)
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. -->

`ion-datetime` has a state variable `isPresented`, which is never set
and goes unused except to add a CSS class (also unused).

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

State variable and class removed.

## 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-08-09 14:14:54 +00:00
Sean Perkins
aa326a6eda chore(react): update v18 test app to use vite (#27935)
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. -->

React 17/18 test apps use react-scripts. This does not align with our
starter templates.

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

- Updates the React 18 test app to use Vite
- Leaves the React 17 test app on react-scripts for now
- Updates test app dependencies to align with the react starters

## 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-08-09 14:12:51 +00:00
Fabio Costa
1cf1eca002 fix(tab-button): update event type interface on React (#27950)
closes #27949

---------

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

- Typescript is complaining about the `onClick` event type of the
`IonTabButton`

```ts
const App: React.FC = () => {
  async function handleTabClick(e: CustomEvent<HTMLIonTabButtonElement>) {
    alert(e.detail.tab);
  }

  return (
    <IonTabButton tab="myTab" onClick={handleTabClick}>
  );
};
```

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

- `onClick` handler uses `CustomEvent` type
- Typescript does not error

## 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-08-09 14:05:39 +00:00
Liam DeBeasi
eb19c289d6 fix(datetime): changing months work if partially visible (#27917)
Issue number: resolves #27913

---------

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

When determining what the changed month is, we grab the element at the
center of the datetime and then grab the nearest calendar month. This
works fine if the datetime is fully in view, but if the center point is
out of the viewport then this will return `null`. As a result, scrolling
in the datetime will break.

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

- We now check scroll position instead of querying for DOM elements at
coordinates. This allows the view to continue to update even if the
entire calendar body is outside the viewport.

## 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.11690996559.1019674a`
2023-08-04 18:41:25 +00:00
Liam DeBeasi
a0e6ac6013 fix(many): overlays present if isOpen is true on load (#27933)
Issue number: resolves #27928 

---------

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

Action sheet, alert, picker, and toast did not have logic where the
overlay presents if `isOpen="true"` on load. Modal, popover, and loading
had this logic but did not have test coverage.

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

- Updated action sheet, alert, picker, and toast to present if
`isOpen="true"` on load
- Added test coverage to all overlays for this functionality.

## 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.3-dev.11691156718.1638345c`
2023-08-04 17:00:21 +00:00
Liam DeBeasi
da55ab949e fix(modal): setCurrentBreakpoint respects animated prop (#27924)
Issue number: resolves #27923

---------

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

`setCurrentBreakpoint` still animates even when `animated: false`.

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

- `setCurrentBreakpoint` jumps directly to the new breakpoint when
`animated: false`

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

Updated design doc:
https://github.com/ionic-team/ionic-framework-design-documents/pull/137
Dev build: `7.2.3-dev.11691069391.1d91d2be`
2023-08-04 15:14:21 +00:00
Liam DeBeasi
a0b3ef02af fix(item-sliding): account for options added before watcher (#27915)
Issue number: resolves #27910

---------

<!-- 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 is an edge case in our `ion-item-sliding` code where options can
be added after the `querySelectorAll` has been run in `updateOptions`
but before `watchForOptions` has been called which causes us to miss the
newly created options. These options can never be shown as a result.

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

- `watchForOptions` is called before the initial `updateOptions` call so
that we can re-run `updateOptions` in the event that options are added
while that first call is running.

## 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.11690983626.19a2a8cb`
2023-08-02 19:20:22 +00:00
Liam DeBeasi
9500769f11 fix(nav): improve reliability of swipe back gesture when quickly swiping back (#27904)
Issue number: resolves #27893

---------

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

This is another instance of
https://github.com/ionic-team/ionic-framework/issues/22895. The
`progressCallback` function is fires asynchronously, so it's possible
for the gesture start and end callbacks to run before the animation is
ever set in `progressCallback`. When this happens, the animation gets
locked up.

I previously fixed this in
https://github.com/ionic-team/ionic-framework/pull/23527 for
`ion-router-outlet`, but I did not fix it for `ion-nav`.

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

- If the gesture has ended by the time `progressCallback` fires, reset
the animation to the beginning so it does not get locked up.

## 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.11690896715.12338339`
2023-08-02 18:48:34 +00:00
Liam DeBeasi
5992c619ad merge release-7.2.2
Release 7.2.2
2023-08-02 13:09:29 -04:00
ionitron
01857dd315 chore(): update package lock files 2023-08-02 15:59:53 +00:00
ionitron
dbe6f390ef v7.2.2 2023-08-02 15:59:37 +00:00
Liam DeBeasi
0c117cfe7f fix(select): popover uses modern form syntax (#27818)
Issue number: resolves #27071, resolves #27786

---------

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

`ion-select-popover` is using the legacy syntax for `ion-checkbox` and
`ion-radio`.

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

- Migrated checkbox and radio to use the modern syntax
- Updated the visual rendering tests to also check the checked state. I
noticed during development that I accidentally broke the checked state,
so I added tests so we can't accidentally break it again.

A couple notes on other screenshot diffs:

- On iOS, the item border line now extends past the radio circle. This
is an intentional behavior and aligns with native iOS. Having the radio
in the "start" slot is typically only done when the content in the
default slot is another interactive element (like and input) and not a
text label.
- On MD, the control heights are smaller. When comparing with
https://material-components.github.io/material-components-web-catalog/#/component/select,
the new behavior seems to be correct. Both the spec and Ionic item
heights are 48px. Interestingly, the radios in `ion-select-popover` have
always been 48px tall, but the checkboxes were 54px. Now both the radios
and checkboxes are 48px.

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

---------

Co-authored-by: amandaejohnston <amandaejohnston@users.noreply.github.com>
Co-authored-by: ionitron <hi@ionicframework.com>
2023-08-01 17:35:51 +00:00
Liam DeBeasi
f14c440d63 fix(input, textarea): input does not block floating label (#27870)
Issue number: resolves #27812

---------

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

We currently have CSS to ensure the floating/stacked label is not
obscured by the input/textarea when using the outline styles. However,
it was discovered that this scenario can happen with any
floating/stacked label not just when the input/textarea is using the
outline style.

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

- Floating/stacked labels appear on top of the input/textarea regardless
of fill mode.

## 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.1-dev.11690464203.1b2b4419`

---------

Co-authored-by: ionitron <hi@ionicframework.com>
2023-08-01 15:48:55 +00:00
Liam DeBeasi
bd1910ba69 fix(datetime-button): render correct text when passing partial date values (#27816)
Issue number: resolves #27797

---------

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

Datetime Button passes a parsed value to one of the many text formatting
utilities we have, such as `getMonthAndYear`. However, developers can
pass partial date values such as `2022` or `2022-04` (April 2022).
According to
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format,
these are still valid date strings.

However, the `parseDate` utility does not add fallback values. So
passing `2022` will cause the `day` and `month` fields to be
`undefined`. This means that `getNormalizedDate` passes `'//2022'` to
the `Date` constructor.

Some browsers, such as Chrome, will automatically account for the stray
slashes and still return a valid date. Other browsers, such as Safari,
do not do this and will either return "Invalid Date" or throw an error.

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

- Date normalizing utility now has fallback values so we always pass in
a valid date. In the example above, `getNormalizedDate` will now pass
`'1/1/2022'` instead of `'//2022'` to the `Date` constructor.
- Refactored other utils that use `new Date` to make use of
`getNormalizedDate` since they are also impacted.

Note: I added an E2E test instead of a spec test because I want to test
cross-browser behavior to ensure consistency.

## 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-08-01 15:44:05 +00:00
83 changed files with 6860 additions and 20163 deletions

View File

@@ -3,6 +3,39 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
### Bug Fixes
* **button:** hidden button is added when form is set async ([#27955](https://github.com/ionic-team/ionic-framework/issues/27955)) ([e9fa300](https://github.com/ionic-team/ionic-framework/commit/e9fa30002bd5dec4f2f56a15c84eec1b3e794942)), closes [#27952](https://github.com/ionic-team/ionic-framework/issues/27952)
* **datetime:** changing months work if partially visible ([#27917](https://github.com/ionic-team/ionic-framework/issues/27917)) ([eb19c28](https://github.com/ionic-team/ionic-framework/commit/eb19c289d6581639f6df7aff002bebdf2b27d31c)), closes [#27913](https://github.com/ionic-team/ionic-framework/issues/27913)
* **item-sliding:** account for options added before watcher ([#27915](https://github.com/ionic-team/ionic-framework/issues/27915)) ([a0b3ef0](https://github.com/ionic-team/ionic-framework/commit/a0b3ef02af718e232246515bb873ad8c090fa55d)), closes [#27910](https://github.com/ionic-team/ionic-framework/issues/27910)
* **many:** overlays present if isOpen is true on load ([#27933](https://github.com/ionic-team/ionic-framework/issues/27933)) ([a0e6ac6](https://github.com/ionic-team/ionic-framework/commit/a0e6ac6013552c5e3acdde33575d4aaf4d4c0bda)), closes [#27928](https://github.com/ionic-team/ionic-framework/issues/27928)
* **modal:** setCurrentBreakpoint respects animated prop ([#27924](https://github.com/ionic-team/ionic-framework/issues/27924)) ([da55ab9](https://github.com/ionic-team/ionic-framework/commit/da55ab949ef1894738da5a6241176089b7a2b6e3)), closes [#27923](https://github.com/ionic-team/ionic-framework/issues/27923)
* **nav:** improve reliability of swipe back gesture when quickly swiping back ([#27904](https://github.com/ionic-team/ionic-framework/issues/27904)) ([9500769](https://github.com/ionic-team/ionic-framework/commit/9500769f114d180613f0340b1a328b5e631b7188)), closes [#27893](https://github.com/ionic-team/ionic-framework/issues/27893)
* **tab-button:** update event type interface on React ([#27950](https://github.com/ionic-team/ionic-framework/issues/27950)) ([1cf1eca](https://github.com/ionic-team/ionic-framework/commit/1cf1eca00239f3e98854466116e42f9a2e7b4590)), closes [#27949](https://github.com/ionic-team/ionic-framework/issues/27949)
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
### Bug Fixes
* **datetime-button:** render correct text when passing partial date values ([#27816](https://github.com/ionic-team/ionic-framework/issues/27816)) ([bd1910b](https://github.com/ionic-team/ionic-framework/commit/bd1910ba69348877ad5f99d9db2b59d06693b91e)), closes [#27797](https://github.com/ionic-team/ionic-framework/issues/27797)
* **input, textarea:** input does not block floating label ([#27870](https://github.com/ionic-team/ionic-framework/issues/27870)) ([f14c440](https://github.com/ionic-team/ionic-framework/commit/f14c440d6321ef9f168b272338e5cd21cab384ef)), closes [#27812](https://github.com/ionic-team/ionic-framework/issues/27812)
* **item-options:** use correct safe area padding ([#27853](https://github.com/ionic-team/ionic-framework/issues/27853)) ([0b8f1bc](https://github.com/ionic-team/ionic-framework/commit/0b8f1bc7dd4170a2a8c9ed3aede173dd489b25ea))
* **radio:** radios can be focused and are announced with group ([#27817](https://github.com/ionic-team/ionic-framework/issues/27817)) ([ba2f49b](https://github.com/ionic-team/ionic-framework/commit/ba2f49b8a460520d20ac198db800ea2d9e5b015f)), closes [#27438](https://github.com/ionic-team/ionic-framework/issues/27438)
* **react, vue:** custom animations are used when going back ([#27895](https://github.com/ionic-team/ionic-framework/issues/27895)) ([824033f](https://github.com/ionic-team/ionic-framework/commit/824033f1d4b4a3e5d4c6a978a39e5bb1f33b5bb4)), closes [#27873](https://github.com/ionic-team/ionic-framework/issues/27873)
* **select:** popover uses modern form syntax ([#27818](https://github.com/ionic-team/ionic-framework/issues/27818)) ([0c117cf](https://github.com/ionic-team/ionic-framework/commit/0c117cfe7f383b7c7837d27de5a6eee12ddd6c2f)), closes [#27071](https://github.com/ionic-team/ionic-framework/issues/27071) [#27786](https://github.com/ionic-team/ionic-framework/issues/27786)
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)

View File

@@ -3,6 +3,37 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
### Bug Fixes
* **button:** hidden button is added when form is set async ([#27955](https://github.com/ionic-team/ionic-framework/issues/27955)) ([e9fa300](https://github.com/ionic-team/ionic-framework/commit/e9fa30002bd5dec4f2f56a15c84eec1b3e794942)), closes [#27952](https://github.com/ionic-team/ionic-framework/issues/27952)
* **datetime:** changing months work if partially visible ([#27917](https://github.com/ionic-team/ionic-framework/issues/27917)) ([eb19c28](https://github.com/ionic-team/ionic-framework/commit/eb19c289d6581639f6df7aff002bebdf2b27d31c)), closes [#27913](https://github.com/ionic-team/ionic-framework/issues/27913)
* **item-sliding:** account for options added before watcher ([#27915](https://github.com/ionic-team/ionic-framework/issues/27915)) ([a0b3ef0](https://github.com/ionic-team/ionic-framework/commit/a0b3ef02af718e232246515bb873ad8c090fa55d)), closes [#27910](https://github.com/ionic-team/ionic-framework/issues/27910)
* **many:** overlays present if isOpen is true on load ([#27933](https://github.com/ionic-team/ionic-framework/issues/27933)) ([a0e6ac6](https://github.com/ionic-team/ionic-framework/commit/a0e6ac6013552c5e3acdde33575d4aaf4d4c0bda)), closes [#27928](https://github.com/ionic-team/ionic-framework/issues/27928)
* **modal:** setCurrentBreakpoint respects animated prop ([#27924](https://github.com/ionic-team/ionic-framework/issues/27924)) ([da55ab9](https://github.com/ionic-team/ionic-framework/commit/da55ab949ef1894738da5a6241176089b7a2b6e3)), closes [#27923](https://github.com/ionic-team/ionic-framework/issues/27923)
* **nav:** improve reliability of swipe back gesture when quickly swiping back ([#27904](https://github.com/ionic-team/ionic-framework/issues/27904)) ([9500769](https://github.com/ionic-team/ionic-framework/commit/9500769f114d180613f0340b1a328b5e631b7188)), closes [#27893](https://github.com/ionic-team/ionic-framework/issues/27893)
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
### Bug Fixes
* **datetime-button:** render correct text when passing partial date values ([#27816](https://github.com/ionic-team/ionic-framework/issues/27816)) ([bd1910b](https://github.com/ionic-team/ionic-framework/commit/bd1910ba69348877ad5f99d9db2b59d06693b91e)), closes [#27797](https://github.com/ionic-team/ionic-framework/issues/27797)
* **input, textarea:** input does not block floating label ([#27870](https://github.com/ionic-team/ionic-framework/issues/27870)) ([f14c440](https://github.com/ionic-team/ionic-framework/commit/f14c440d6321ef9f168b272338e5cd21cab384ef)), closes [#27812](https://github.com/ionic-team/ionic-framework/issues/27812)
* **item-options:** use correct safe area padding ([#27853](https://github.com/ionic-team/ionic-framework/issues/27853)) ([0b8f1bc](https://github.com/ionic-team/ionic-framework/commit/0b8f1bc7dd4170a2a8c9ed3aede173dd489b25ea))
* **radio:** radios can be focused and are announced with group ([#27817](https://github.com/ionic-team/ionic-framework/issues/27817)) ([ba2f49b](https://github.com/ionic-team/ionic-framework/commit/ba2f49b8a460520d20ac198db800ea2d9e5b015f)), closes [#27438](https://github.com/ionic-team/ionic-framework/issues/27438)
* **select:** popover uses modern form syntax ([#27818](https://github.com/ionic-team/ionic-framework/issues/27818)) ([0c117cf](https://github.com/ionic-team/ionic-framework/commit/0c117cfe7f383b7c7837d27de5a6eee12ddd6c2f)), closes [#27071](https://github.com/ionic-team/ionic-framework/issues/27071) [#27786](https://github.com/ionic-team/ionic-framework/issues/27786)
## [7.2.1](https://github.com/ionic-team/ionic-framework/compare/v7.2.0...v7.2.1) (2023-07-26)

View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/core",
"version": "7.2.1",
"version": "7.2.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
"version": "7.2.1",
"version": "7.2.3",
"license": "MIT",
"dependencies": {
"@stencil/core": "^3.4.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "7.2.1",
"version": "7.2.3",
"description": "Base components for Ionic",
"keywords": [
"ionic",

View File

@@ -2,6 +2,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Watch, Component, Element, Event, Host, Method, Prop, h, readTask } from '@stencil/core';
import type { Gesture } from '@utils/gesture';
import { createButtonActiveGesture } from '@utils/gesture/button-active';
import { raf } from '@utils/helpers';
import {
BACKDROP,
createDelegateController,
@@ -318,25 +319,32 @@ export class ActionSheet implements ComponentInterface, OverlayInterface {
componentDidLoad() {
/**
* Do not create gesture if:
* 1. A gesture already exists
* 2. App is running in MD mode
* 3. A wrapper ref does not exist
* Only create gesture if:
* 1. A gesture does not already exist
* 2. App is running in iOS mode
* 3. A wrapper ref exists
* 4. A group ref exists
*/
const { groupEl, wrapperEl } = this;
if (this.gesture || getIonMode(this) === 'md' || !wrapperEl || !groupEl) {
return;
if (!this.gesture && getIonMode(this) === 'ios' && wrapperEl && groupEl) {
readTask(() => {
const isScrollable = groupEl.scrollHeight > groupEl.clientHeight;
if (!isScrollable) {
this.gesture = createButtonActiveGesture(wrapperEl, (refEl: HTMLElement) =>
refEl.classList.contains('action-sheet-button')
);
this.gesture.enable(true);
}
});
}
readTask(() => {
const isScrollable = groupEl.scrollHeight > groupEl.clientHeight;
if (!isScrollable) {
this.gesture = createButtonActiveGesture(wrapperEl, (refEl: HTMLElement) =>
refEl.classList.contains('action-sheet-button')
);
this.gesture.enable(true);
}
});
/**
* If action sheet was rendered with isOpen="true"
* then we should open action sheet immediately.
*/
if (this.isOpen === true) {
raf(() => this.present());
}
}
render() {

View File

@@ -30,5 +30,10 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ config, title }) =>
await expect(actionSheet).toBeHidden();
});
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-action-sheet is-open="true"></ion-action-sheet>', config);
await expect(page.locator('ion-action-sheet')).toBeVisible();
});
});
});

View File

@@ -3,6 +3,7 @@ import { Component, Element, Event, Host, Listen, Method, Prop, Watch, forceUpda
import { ENABLE_HTML_CONTENT_DEFAULT } from '@utils/config';
import type { Gesture } from '@utils/gesture';
import { createButtonActiveGesture } from '@utils/gesture/button-active';
import { raf } from '@utils/helpers';
import {
createDelegateController,
createTriggerController,
@@ -346,19 +347,25 @@ export class Alert implements ComponentInterface, OverlayInterface {
componentDidLoad() {
/**
* Do not create gesture if:
* 1. A gesture already exists
* 2. App is running in MD mode
* 3. A wrapper ref does not exist
* Only create gesture if:
* 1. A gesture does not already exist
* 2. App is running in iOS mode
* 3. A wrapper ref exists
*/
if (this.gesture || getIonMode(this) === 'md' || !this.wrapperEl) {
return;
if (!this.gesture && getIonMode(this) === 'ios' && this.wrapperEl) {
this.gesture = createButtonActiveGesture(this.wrapperEl, (refEl: HTMLElement) =>
refEl.classList.contains('alert-button')
);
this.gesture.enable(true);
}
this.gesture = createButtonActiveGesture(this.wrapperEl, (refEl: HTMLElement) =>
refEl.classList.contains('alert-button')
);
this.gesture.enable(true);
/**
* If alert was rendered with isOpen="true"
* then we should open alert immediately.
*/
if (this.isOpen === true) {
raf(() => this.present());
}
}
/**

View File

@@ -29,5 +29,10 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ config, title }) =>
await ionAlertDidDismiss.next();
await expect(alert).toBeHidden();
});
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-alert is-open="true"></ion-alert>', config);
await expect(page.locator('ion-alert')).toBeVisible();
});
});
});

View File

@@ -153,19 +153,27 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
*/
@Event() ionBlur!: EventEmitter<void>;
connectedCallback(): void {
// Allow form to be submitted through `ion-button`
if (this.type !== 'button' && hasShadowDom(this.el)) {
this.formEl = this.findForm();
if (this.formEl) {
// Create a hidden native button inside of the form
this.formButtonEl = document.createElement('button');
this.formButtonEl.type = this.type;
this.formButtonEl.style.display = 'none';
// Only submit if the button is not disabled.
this.formButtonEl.disabled = this.disabled;
this.formEl.appendChild(this.formButtonEl);
private renderHiddenButton() {
const formEl = (this.formEl = this.findForm());
if (formEl) {
const { formButtonEl } = this;
/**
* If the form already has a rendered form button
* then do not append a new one again.
*/
if (formButtonEl !== null && formEl.contains(formButtonEl)) {
return;
}
// Create a hidden native button inside of the form
const newFormButtonEl = (this.formButtonEl = document.createElement('button'));
newFormButtonEl.type = this.type;
newFormButtonEl.style.display = 'none';
// Only submit if the button is not disabled.
newFormButtonEl.disabled = this.disabled;
formEl.appendChild(newFormButtonEl);
}
}
@@ -314,6 +322,11 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
if (fill == null) {
fill = this.inToolbar || this.inListHeader ? 'clear' : 'solid';
}
{
type !== 'button' && this.renderHiddenButton();
}
return (
<Host
onClick={this.handleClick}

View File

@@ -130,6 +130,29 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
expect(submitEvent).not.toHaveReceivedEvent();
});
test('should submit the form by id when form is set async', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/27952',
});
await page.setContent(
`
<form id="myForm"></form>
<ion-button type="submit">Submit</ion-button>
`,
config
);
const submitEvent = await page.spyOnEvent('submit');
const button = page.locator('ion-button');
await button.evaluate((el: HTMLIonButtonElement) => (el.form = 'myForm'));
await page.click('ion-button');
expect(submitEvent).toHaveReceivedEvent();
});
});
test.describe(title('should throw a warning if the form cannot be found'), () => {

View File

@@ -0,0 +1,31 @@
import { newSpecPage } from '@stencil/core/testing';
import { Button } from '../../button';
describe('Button: Hidden Form Button', () => {
it('should not add multiple buttons to the form', async () => {
const page = await newSpecPage({
components: [Button],
html: `
<form id="my-form"></form>
<ion-button form="my-form" type="submit">Submit</ion-button>
`,
});
const getButtons = () => {
return page.body.querySelectorAll('form button');
};
const form = page.body.querySelectorAll('form');
const button = page.body.querySelector('ion-button');
await page.waitForChanges();
expect(getButtons().length).toEqual(1);
// Re-render the component
button.color = 'danger';
await page.waitForChanges();
expect(getButtons().length).toEqual(1);
});
});

View File

@@ -122,6 +122,42 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
await expect(dateTarget).toContainText('May 10, 2023');
});
test('should set only month and year when only passing month and year', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/27797',
});
await page.setContent(
`
<ion-datetime-button locale="en-US" datetime="datetime"></ion-datetime-button>
<ion-datetime id="datetime" value="2022-01" presentation="month-year"></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
await expect(page.locator('#date-button')).toContainText('January 2022');
await expect(page.locator('#time-button')).toBeHidden();
});
test('should set only year when passing only year', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/27797',
});
await page.setContent(
`
<ion-datetime-button locale="en-US" datetime="datetime"></ion-datetime-button>
<ion-datetime id="datetime" value="2022" presentation="year"></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
await expect(page.locator('#date-button')).toContainText('2022');
await expect(page.locator('#time-button')).toBeHidden();
});
});
test.describe(title('datetime-button: locale'), () => {

View File

@@ -138,7 +138,6 @@ export class Datetime implements ComponentInterface {
@Element() el!: HTMLIonDatetimeElement;
@State() isPresented = false;
@State() isTimePopoverOpen = false;
/**
@@ -883,21 +882,18 @@ export class Datetime implements ComponentInterface {
const getChangedMonth = (parts: DatetimeParts): DatetimeParts | undefined => {
const box = calendarBodyRef.getBoundingClientRect();
const root = this.el!.shadowRoot!;
/**
* Get the element that is in the center of the calendar body.
* This will be an element inside of the active month.
* If the current scroll position is all the way to the left
* then we have scrolled to the previous month.
* Otherwise, assume that we have scrolled to the next
* month. We have a tolerance of 2px to account for
* sub pixel rendering.
*
* Check below the next line ensures that we did not
* swipe and abort (i.e. we swiped but we are still on the current month).
*/
const elementAtCenter = root.elementFromPoint(box.x + box.width / 2, box.y + box.height / 2);
/**
* If there is no element then the
* component may be re-rendering on a slow device.
*/
if (!elementAtCenter) return;
const month = elementAtCenter.closest('.calendar-month');
if (!month) return;
const month = calendarBodyRef.scrollLeft <= 2 ? startMonth : endMonth;
/**
* The edge of the month must be lined up with
@@ -2364,19 +2360,7 @@ export class Datetime implements ComponentInterface {
}
render() {
const {
name,
value,
disabled,
el,
color,
isPresented,
readonly,
showMonthAndYear,
preferWheel,
presentation,
size,
} = this;
const { name, value, disabled, el, color, readonly, showMonthAndYear, preferWheel, presentation, size } = this;
const mode = getIonMode(this);
const isMonthAndYearPresentation =
presentation === 'year' || presentation === 'month' || presentation === 'month-year';
@@ -2396,7 +2380,6 @@ export class Datetime implements ComponentInterface {
class={{
...createColorClasses(color, {
[mode]: true,
['datetime-presented']: isPresented,
['datetime-readonly']: readonly,
['datetime-disabled']: disabled,
'show-month-and-year': shouldShowMonthAndYear,

View File

@@ -301,21 +301,20 @@ configs({ directions: ['ltr'], modes: ['ios'] }).forEach(({ title, config }) =>
<ion-datetime
min="2022-01-15"
value="2022-02-01"
presentation="date"
presentation="month-year"
></ion-datetime>
`,
config
);
const datetime = page.locator('ion-datetime');
const monthYearToggle = page.locator('ion-datetime .calendar-month-year');
const monthColumnItems = page.locator('ion-datetime .month-column .picker-item:not(.picker-item-empty)');
const ionChange = await page.spyOnEvent('ionChange');
await monthYearToggle.click();
await page.waitForChanges();
await page.waitForSelector('.datetime-ready');
await monthColumnItems.nth(0).click(); // switch to January
await page.waitForChanges();
await ionChange.next();
await expect(datetime).toHaveJSProperty('value', '2022-01-15T00:00:00');
});

View File

@@ -34,13 +34,14 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
`,
config
);
await page.waitForSelector('.datetime-ready');
const datetime = page.locator('ion-datetime');
const activeDayButton = page.locator('.calendar-day-active');
const monthYearButton = page.locator('.calendar-month-year');
const monthColumn = page.locator('.month-column');
const yearColumn = page.locator('.year-column');
const ionChange = await page.spyOnEvent('ionChange');
await datetime.evaluate((el: HTMLIonDatetimeElement) => (el.value = '2021-10-05'));
@@ -49,11 +50,9 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await page.waitForChanges();
// Select October 2021
// The year will automatically switch to 2021 when selecting 10
await monthColumn.locator('.picker-item[data-value="10"]').click();
await page.waitForChanges();
await yearColumn.locator('.picker-item[data-value="2021"]').click();
await page.waitForChanges();
await ionChange.next();
// Close month/year picker
await monthYearButton.click();

View File

@@ -113,7 +113,7 @@ export const generateDayAriaLabel = (locale: string, today: boolean, refParts: D
/**
* MM/DD/YYYY will return midnight in the user's timezone.
*/
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
const date = getNormalizedDate(refParts);
const labelString = new Intl.DateTimeFormat(locale, {
weekday: 'long',
@@ -134,7 +134,7 @@ export const generateDayAriaLabel = (locale: string, today: boolean, refParts: D
* Used for the header in MD mode.
*/
export const getMonthAndDay = (locale: string, refParts: DatetimeParts) => {
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
const date = getNormalizedDate(refParts);
return new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric', timeZone: 'UTC' }).format(
date
);
@@ -147,7 +147,7 @@ export const getMonthAndDay = (locale: string, refParts: DatetimeParts) => {
* Example: May 2021
*/
export const getMonthAndYear = (locale: string, refParts: DatetimeParts) => {
const date = new Date(`${refParts.month}/${refParts.day}/${refParts.year} GMT+0000`);
const date = getNormalizedDate(refParts);
return new Intl.DateTimeFormat(locale, { month: 'long', year: 'numeric', timeZone: 'UTC' }).format(date);
};
@@ -183,11 +183,25 @@ export const getYear = (locale: string, refParts: DatetimeParts) => {
return getLocalizedDateTime(locale, refParts, { year: 'numeric' });
};
const getNormalizedDate = (refParts: DatetimeParts) => {
/**
* Given reference parts, return a JS Date object
* with a normalized time.
*/
export const getNormalizedDate = (refParts: DatetimeParts) => {
const timeString =
refParts.hour !== undefined && refParts.minute !== undefined ? ` ${refParts.hour}:${refParts.minute}` : '';
return new Date(`${refParts.month}/${refParts.day}/${refParts.year}${timeString} GMT+0000`);
/**
* We use / notation here for the date
* so we do not need to do extra work and pad values with zeroes.
* Values such as YYYY-MM are still valid, so
* we add fallback values so we still get
* a valid date otherwise we will pass in a string
* like "//2023". Some browsers, such as Chrome, will
* account for this and still return a valid date. However,
* this is not a consistent behavior across all browsers.
*/
return new Date(`${refParts.month ?? 1}/${refParts.day ?? 1}/${refParts.year ?? 2023}${timeString} GMT+0000`);
};
/**

View File

@@ -82,8 +82,6 @@
:host(.input-fill-outline) .label-text-wrapper,
:host(.input-fill-outline) .label-text-wrapper {
position: relative;
z-index: 1;
}
/**

View File

@@ -581,6 +581,13 @@
@include transform-origin(start, top);
max-width: 100%;
/**
* The 2 ensures the label
* remains on top of any browser
* autofill background too.
*/
z-index: 2;
}
/**

View File

@@ -186,9 +186,6 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
expect(await input.screenshot()).toMatchSnapshot(screenshot(`input-label-slot-truncate`));
});
});
});
configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
test.describe(title('input: async label'), () => {
test('input should re-render when label slot is added async', async ({ page }) => {
await page.setContent(
@@ -213,4 +210,27 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
expect(await input.screenshot()).toMatchSnapshot(screenshot(`input-async-label`));
});
});
test.describe(title('input: floating/stacked label layering'), () => {
test('label should not be covered by text field', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/27812',
});
await page.setContent(
`
<style>
.custom-input .native-wrapper {
background: pink;
}
</style>
<ion-input class="custom-input" label="My Label" label-placement="stacked"></ion-input>
`,
config
);
const input = page.locator('ion-input');
expect(await input.screenshot()).toMatchSnapshot(screenshot(`input-label-layering`));
});
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -76,12 +76,19 @@ export class ItemSliding implements ComponentInterface {
this.item = el.querySelector('ion-item');
this.contentEl = findClosestIonContent(el);
await this.updateOptions();
/**
* The MutationObserver needs to be added before we
* call updateOptions below otherwise we may miss
* ion-item-option elements that are added to the DOM
* while updateOptions is running and before the MutationObserver
* has been initialized.
*/
this.mutationObserver = watchForOptions<HTMLIonItemOptionElement>(el, 'ion-item-option', async () => {
await this.updateOptions();
});
await this.updateOptions();
this.gesture = (await import('../../utils/gesture')).createGesture({
el,
gestureName: 'item-swipe',

View File

@@ -23,5 +23,10 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await ionLoadingDidDismiss.next();
await expect(loading).toBeHidden();
});
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-loading is-open="true"></ion-loading>', config);
await expect(page.locator('ion-loading')).toBeVisible();
});
});
});

View File

@@ -31,6 +31,12 @@ export interface MoveSheetToBreakpointOptions {
* `true` if the sheet can be transitioned and dismissed off the view.
*/
canDismiss?: boolean;
/**
* If `true`, the sheet will animate to the breakpoint.
* If `false`, the sheet will jump directly to the breakpoint.
*/
animated: boolean;
}
export const createSheetGesture = (
@@ -246,11 +252,17 @@ export const createSheetGesture = (
breakpoint: closest,
breakpointOffset: offset,
canDismiss: canDismissBlocksGesture,
/**
* The swipe is user-driven, so we should
* always animate when the gesture ends.
*/
animated: true,
});
};
const moveSheetToBreakpoint = (options: MoveSheetToBreakpointOptions) => {
const { breakpoint, canDismiss, breakpointOffset } = options;
const { breakpoint, canDismiss, breakpointOffset, animated } = options;
/**
* canDismiss should only prevent snapping
* when users are trying to dismiss. If canDismiss
@@ -360,7 +372,7 @@ export const createSheetGesture = (
},
{ oneTimeCallback: true }
)
.progressEnd(1, 0, 500);
.progressEnd(1, 0, animated ? 500 : 0);
});
};

View File

@@ -761,7 +761,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
return;
}
const { currentBreakpoint, moveSheetToBreakpoint, canDismiss, breakpoints } = this;
const { currentBreakpoint, moveSheetToBreakpoint, canDismiss, breakpoints, animated } = this;
if (currentBreakpoint === breakpoint) {
return;
@@ -772,6 +772,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
breakpoint,
breakpointOffset: 1 - currentBreakpoint!,
canDismiss: canDismiss !== undefined && canDismiss !== true && breakpoints![0] === 0,
animated,
});
await this.sheetTransition;
this.sheetTransition = undefined;

View File

@@ -20,5 +20,10 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await ionModalDidDismiss.next();
await expect(modal).toBeHidden();
});
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-modal is-open="true"></ion-modal>', config);
await expect(page.locator('ion-modal')).toBeVisible();
});
});
});

View File

@@ -31,7 +31,7 @@ import { VIEW_STATE_ATTACHED, VIEW_STATE_DESTROYED, VIEW_STATE_NEW, convertToVie
export class Nav implements NavOutlet {
private transInstr: TransitionInstruction[] = [];
private sbAni?: Animation;
private animationEnabled = true;
private gestureOrAnimationInProgress = false;
private useRouter = false;
private isTransitioning = false;
private destroyed = false;
@@ -869,7 +869,36 @@ export class Nav implements NavOutlet {
// or if it is a portal (modal, actionsheet, etc.)
const opts = ti.opts!;
const progressCallback = opts.progressAnimation ? (ani: Animation | undefined) => (this.sbAni = ani) : undefined;
const progressCallback = opts.progressAnimation
? (ani: Animation | undefined) => {
/**
* Because this progress callback is called asynchronously
* it is possible for the gesture to start and end before
* the animation is ever set. In that scenario, we should
* immediately call progressEnd so that the transition promise
* resolves and the gesture does not get locked up.
*/
if (ani !== undefined && !this.gestureOrAnimationInProgress) {
this.gestureOrAnimationInProgress = true;
ani.onFinish(
() => {
this.gestureOrAnimationInProgress = false;
},
{ oneTimeCallback: true }
);
/**
* Playing animation to beginning
* with a duration of 0 prevents
* any flickering when the animation
* is later cleaned up.
*/
ani.progressEnd(0, 0, 0);
} else {
this.sbAni = ani;
}
}
: undefined;
const mode = getIonMode(this);
const enteringEl = enteringView.element!;
const leavingEl = leavingView && leavingView.element!;
@@ -1008,15 +1037,16 @@ export class Nav implements NavOutlet {
private canStart(): boolean {
return (
!this.gestureOrAnimationInProgress &&
!!this.swipeGesture &&
!this.isTransitioning &&
this.transInstr.length === 0 &&
this.animationEnabled &&
this.canGoBackSync()
);
}
private onStart() {
this.gestureOrAnimationInProgress = true;
this.pop({ direction: 'back', progressAnimation: true });
}
@@ -1028,10 +1058,9 @@ export class Nav implements NavOutlet {
private onEnd(shouldComplete: boolean, stepValue: number, dur: number) {
if (this.sbAni) {
this.animationEnabled = false;
this.sbAni.onFinish(
() => {
this.animationEnabled = true;
this.gestureOrAnimationInProgress = false;
},
{ oneTimeCallback: true }
);
@@ -1055,6 +1084,8 @@ export class Nav implements NavOutlet {
}
this.sbAni.progressEnd(shouldComplete ? 1 : 0, newStepValue, dur);
} else {
this.gestureOrAnimationInProgress = false;
}
}

View File

@@ -1,5 +1,6 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core';
import { raf } from '@utils/helpers';
import {
createDelegateController,
createTriggerController,
@@ -199,6 +200,16 @@ export class Picker implements ComponentInterface, OverlayInterface {
setOverlayId(this.el);
}
componentDidLoad() {
/**
* If picker was rendered with isOpen="true"
* then we should open picker immediately.
*/
if (this.isOpen === true) {
raf(() => this.present());
}
}
/**
* Present the picker overlay after it has been created.
*/

View File

@@ -23,5 +23,10 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await ionPickerDidDismiss.next();
await expect(picker).toBeHidden();
});
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-picker is-open="true"></ion-picker>', config);
await expect(page.locator('ion-picker')).toBeVisible();
});
});
});

View File

@@ -0,0 +1,11 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('popover: isOpen'), () => {
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-popover is-open="true"></ion-popover>', config);
await expect(page.locator('ion-popover')).toBeVisible();
});
});
});

View File

@@ -1,7 +1,7 @@
@import "./select-popover";
@import "./select-popover.md.vars";
ion-list ion-radio {
ion-list ion-radio::part(container) {
opacity: 0;
}

View File

@@ -1,5 +1,5 @@
import type { ComponentInterface } from '@stencil/core';
import { Element, Component, Host, Prop, h } from '@stencil/core';
import { Element, Component, Host, Prop, h, forceUpdate } from '@stencil/core';
import { safeCall } from '@utils/overlays';
import { getClassMap } from '@utils/theme';
@@ -116,19 +116,28 @@ export class SelectPopover implements ComponentInterface {
renderCheckboxOptions(options: SelectPopoverOption[]) {
return options.map((option) => (
<ion-item class={getClassMap(option.cssClass)}>
<ion-item
class={{
// TODO FW-4784
'item-checkbox-checked': option.checked,
...getClassMap(option.cssClass),
}}
>
<ion-checkbox
slot="start"
value={option.value}
disabled={option.disabled}
checked={option.checked}
legacy={true}
justify="start"
labelPlacement="end"
onIonChange={(ev) => {
this.setChecked(ev);
this.callOptionHandler(ev);
// TODO FW-4784
forceUpdate(this);
}}
></ion-checkbox>
<ion-label>{option.text}</ion-label>
>
{option.text}
</ion-checkbox>
</ion-item>
));
}
@@ -139,12 +148,16 @@ export class SelectPopover implements ComponentInterface {
return (
<ion-radio-group value={checked} onIonChange={(ev) => this.callOptionHandler(ev)}>
{options.map((option) => (
<ion-item class={getClassMap(option.cssClass)}>
<ion-label>{option.text}</ion-label>
<ion-item
class={{
// TODO FW-4784
'item-radio-checked': option.value === checked,
...getClassMap(option.cssClass),
}}
>
<ion-radio
value={option.value}
disabled={option.disabled}
legacy={true}
onClick={() => this.dismissParentPopover()}
onKeyUp={(ev) => {
if (ev.key === ' ') {
@@ -156,7 +169,9 @@ export class SelectPopover implements ComponentInterface {
this.dismissParentPopover();
}
}}
></ion-radio>
>
{option.text}
</ion-radio>
</ion-item>
))}
</ion-radio-group>

View File

@@ -81,11 +81,11 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
selectPopoverPage = new SelectPopoverPage(page);
});
test('should not have visual regressions with single selection', async () => {
await selectPopoverPage.setup(config, options, false);
await selectPopoverPage.setup(config, checkedOptions, false);
await selectPopoverPage.screenshot(screenshot, 'select-popover-diff');
});
test('should not have visual regressions with multiple selection', async () => {
await selectPopoverPage.setup(config, options, true);
await selectPopoverPage.setup(config, checkedOptions, true);
await selectPopoverPage.screenshot(screenshot, 'select-popover-multiple-diff');
});
});

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -302,4 +302,27 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, screenshot, co
await expect(page.locator('.container')).toHaveScreenshot(screenshot(`textarea-multi-line-sizing`));
});
});
test.describe(title('textarea: floating/stacked label layering'), () => {
test('label should not be covered by text field', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/27812',
});
await page.setContent(
`
<style>
.custom-textarea .native-wrapper {
background: pink;
}
</style>
<ion-textarea class="custom-textarea" label="My Label" label-placement="stacked"></ion-textarea>
`,
config
);
const textarea = page.locator('ion-textarea');
expect(await textarea.screenshot()).toMatchSnapshot(screenshot(`textarea-label-layering`));
});
});
});

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -82,8 +82,6 @@
:host(.textarea-fill-outline) .label-text-wrapper,
:host(.textarea-fill-outline) .label-text-wrapper {
position: relative;
z-index: 1;
}
/**

View File

@@ -629,6 +629,13 @@
@include padding(0px);
max-width: 100%;
/**
* The 2 ensures the label
* remains on top of any browser
* autofill background too.
*/
z-index: 2;
}
/**

View File

@@ -24,5 +24,10 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
await ionToastDidDismiss.next();
await expect(toast).toBeHidden();
});
test('should open if isOpen is true on load', async ({ page }) => {
await page.setContent('<ion-toast is-open="true"></ion-toast>', config);
await expect(page.locator('ion-toast')).toBeVisible();
});
});
});

View File

@@ -1,6 +1,7 @@
import type { ComponentInterface, EventEmitter } from '@stencil/core';
import { State, Watch, Component, Element, Event, h, Host, Method, Prop } from '@stencil/core';
import { ENABLE_HTML_CONTENT_DEFAULT } from '@utils/config';
import { raf } from '@utils/helpers';
import { printIonWarning } from '@utils/logging';
import {
createDelegateController,
@@ -253,6 +254,16 @@ export class Toast implements ComponentInterface, OverlayInterface {
setOverlayId(this.el);
}
componentDidLoad() {
/**
* If toast was rendered with isOpen="true"
* then we should open toast immediately.
*/
if (this.isOpen === true) {
raf(() => this.present());
}
}
/**
* Present the toast overlay after it has been created.
*/

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
**Note:** Version bump only for package @ionic/docs
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
**Note:** Version bump only for package @ionic/docs
## [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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/docs",
"version": "7.2.1",
"version": "7.2.3",
"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.1"
"version": "7.2.3"
}

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
**Note:** Version bump only for package @ionic/angular-server
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
**Note:** Version bump only for package @ionic/angular-server
## [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

View File

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

View File

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

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
**Note:** Version bump only for package @ionic/angular
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
**Note:** Version bump only for package @ionic/angular
## [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

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular",
"version": "7.2.1",
"version": "7.2.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular",
"version": "7.2.1",
"version": "7.2.3",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.2.1",
"@ionic/core": "^7.2.3",
"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.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.2.tgz",
"integrity": "sha512-KD+dZrrIpKs1BlE3Ffp05yrejSvurUhr0OWP2P/NRax5Dld8wSZj+ETOMkV8uiIPbOTOUbLaAQ7m5ZXduMCYhQ==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -8104,9 +8104,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.2.tgz",
"integrity": "sha512-KD+dZrrIpKs1BlE3Ffp05yrejSvurUhr0OWP2P/NRax5Dld8wSZj+ETOMkV8uiIPbOTOUbLaAQ7m5ZXduMCYhQ==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",

View File

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

View File

@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
**Note:** Version bump only for package @ionic/react-router
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
### Bug Fixes
* **react, vue:** custom animations are used when going back ([#27895](https://github.com/ionic-team/ionic-framework/issues/27895)) ([824033f](https://github.com/ionic-team/ionic-framework/commit/824033f1d4b4a3e5d4c6a978a39e5bb1f33b5bb4)), closes [#27873](https://github.com/ionic-team/ionic-framework/issues/27873)
## [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

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/react-router",
"version": "7.2.1",
"version": "7.2.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/react-router",
"version": "7.2.1",
"version": "7.2.3",
"license": "MIT",
"dependencies": {
"@ionic/react": "^7.2.1",
"@ionic/react": "^7.2.3",
"tslib": "*"
},
"devDependencies": {
@@ -205,9 +205,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.2.tgz",
"integrity": "sha512-KD+dZrrIpKs1BlE3Ffp05yrejSvurUhr0OWP2P/NRax5Dld8wSZj+ETOMkV8uiIPbOTOUbLaAQ7m5ZXduMCYhQ==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -401,11 +401,11 @@
}
},
"node_modules/@ionic/react": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.2.1.tgz",
"integrity": "sha512-z9PSPoBiHsdwotdywGcnln1y01wjLoS09vmCUboc+2qSadVMczcjEuBxfRIvjVbaQt3u8RGHif2+K3AgGNKMeA==",
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.2.2.tgz",
"integrity": "sha512-QFvLTCw/9/7aBFooRUZFD/pIOLgMO5E7AzW9DeJoyjenVp3TBbhKvlz275+2Z4Nmj82nXMZs2xWnmuzgweb2sQ==",
"dependencies": {
"@ionic/core": "7.2.1",
"@ionic/core": "7.2.2",
"ionicons": "^7.0.0",
"tslib": "*"
},
@@ -3663,9 +3663,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.2.tgz",
"integrity": "sha512-KD+dZrrIpKs1BlE3Ffp05yrejSvurUhr0OWP2P/NRax5Dld8wSZj+ETOMkV8uiIPbOTOUbLaAQ7m5ZXduMCYhQ==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -3786,11 +3786,11 @@
"requires": {}
},
"@ionic/react": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.2.1.tgz",
"integrity": "sha512-z9PSPoBiHsdwotdywGcnln1y01wjLoS09vmCUboc+2qSadVMczcjEuBxfRIvjVbaQt3u8RGHif2+K3AgGNKMeA==",
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/@ionic/react/-/react-7.2.2.tgz",
"integrity": "sha512-QFvLTCw/9/7aBFooRUZFD/pIOLgMO5E7AzW9DeJoyjenVp3TBbhKvlz275+2Z4Nmj82nXMZs2xWnmuzgweb2sQ==",
"requires": {
"@ionic/core": "7.2.1",
"@ionic/core": "7.2.2",
"ionicons": "^7.0.0",
"tslib": "*"
}

View File

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

View File

@@ -1,7 +1,6 @@
import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react';
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
import type { PropsWithChildren } from 'react';
import React, { cloneElement, useContext, useEffect, useMemo, useReducer, useRef, useState } from 'react';
import React from 'react';
import { matchPath } from 'react-router-dom';
import { clonePageElement } from './clonePageElement';
@@ -12,93 +11,76 @@ 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 const StackManager = ({ children, ...props }: PropsWithChildren<StackManagerProps>) => {
const { routeInfo } = props;
const {
findViewItemByRouteInfo,
findLeavingViewItemByRouteInfo,
findViewItemByPathname,
createViewItem,
addViewItem,
goBack,
getChildrenToRender,
clearOutlet,
} = useContext(RouteManagerContext);
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;
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 }>();
stackContextValue: StackContextState = {
registerIonPage: this.registerIonPage.bind(this),
isInOutlet: () => true,
};
const forceUpdate = useReducer((x) => x + 1, 0)[1];
private clearOutletTimeout: any;
private pendingPageTransition = false;
const [id] = useState(generateId('routerOutlet'));
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 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);
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);
}
}, []);
useEffect(() => {
const { pathname } = routeInfo;
if (pathname !== prevProps.current?.routeInfo.pathname) {
prevProps.current = props;
handlePageTransition(routeInfo);
} else if (pendingPageTransitionRef.current) {
handlePageTransition(routeInfo);
pendingPageTransitionRef.current = false;
if (this.routerOutletElement) {
this.setupRouterOutlet(this.routerOutletElement);
this.handlePageTransition(this.props.routeInfo);
}
}, [routeInfo]);
}
useEffect(() => {
return () => {
clearOutlet(id);
if (clearOutletTimeout.current) {
clearTimeout(clearOutletTimeout.current);
clearOutletTimeout.current = null;
}
};
}, []);
componentDidUpdate(prevProps: StackManagerProps) {
const { pathname } = this.props.routeInfo;
const { pathname: prevPathname } = prevProps.routeInfo;
const handlePageTransition = (routeInfo: RouteInfo) => {
const routerOutletElement = routerOutletRef.current;
if (!routerOutletElement || !routerOutletElement.commit) {
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) {
/**
* The route outlet has not mounted yet. We need to wait for it to render
* before we can transition the page.
@@ -106,126 +88,142 @@ export const StackManager = ({ children, ...props }: PropsWithChildren<StackMana
* Set a flag to indicate that we should transition the page after
* the component has updated.
*/
pendingPageTransitionRef.current = true;
return;
}
this.pendingPageTransition = true;
} else {
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
let enteringViewItem = findViewItemByRouteInfo(routeInfo, id);
let leavingViewItem = findLeavingViewItemByRouteInfo(routeInfo, id);
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
}
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) {
// 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) {
leavingViewItem.mount = false;
}
} else if (routeInfo.routeOptions?.unmount) {
leavingViewItem.mount = false;
}
}
const enteringRoute = matchRoute(ionRouterOutletRef.current?.props.children, routeInfo) as React.ReactElement;
const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
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.reactElement = enteringRoute;
} else if (enteringRoute) {
enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo);
this.context.addViewItem(enteringViewItem);
}
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 (enteringViewItem && enteringViewItem.ionPageElement) {
/**
* 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.
* then we don't need to transition.
*/
if (enteringViewItem.routeData.match.url !== routeInfo.pathname) {
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!)
) {
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);
}
/**
* 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 && routeInfo.prevRouteLastPathname) {
leavingViewItem = findViewItemByPathname(routeInfo.prevRouteLastPathname, id);
}
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;
/**
* If the entering view is already visible and the leaving view is not, the transition does not need to occur.
* React 18 will unmount and remount IonPage
* elements in development mode when using createRoot.
* This can cause duplicate page transitions to occur.
*/
if (
isViewVisible(enteringViewItem.ionPageElement) &&
leavingViewItem !== undefined &&
!isViewVisible(leavingViewItem.ionPageElement!)
) {
if (oldPageElement === page) {
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.
*/
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.handlePageTransition(routeInfo);
}
// 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 => {
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
const canStart = () => {
const config = getConfig();
const swipeEnabled = config?.getBoolean('swipeBackEnabled', routerOutlet.mode === 'ios');
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
if (!swipeEnabled) {
return false;
}
const propsToUse =
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? prevProps.current!.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const { routeInfo } = this.props;
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
const propsToUse =
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
return (
!!enteringViewItem &&
@@ -249,12 +247,14 @@ export const StackManager = ({ children, ...props }: PropsWithChildren<StackMana
};
const onStart = async () => {
const { routeInfo } = this.props;
const propsToUse =
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? prevProps.current!.routeInfo
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
const leavingViewItem = findViewItemByRouteInfo(routeInfo, id, false);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
/**
* When the gesture starts, kick off
@@ -262,28 +262,30 @@ export const StackManager = ({ children, ...props }: PropsWithChildren<StackMana
* via a swipe gesture.
*/
if (enteringViewItem && leavingViewItem) {
await transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
}
return Promise.resolve();
};
const onEnd = (shouldContinue: boolean) => {
if (shouldContinue) {
skipTransitionRef.current = true;
goBack();
this.skipTransition = true;
this.context.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 =
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
? prevProps.current!.routeInfo
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
? this.prevProps.routeInfo
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
const leavingViewItem = findViewItemByRouteInfo(routeInfo, id, false);
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
/**
* Ionic React has a design defect where it
@@ -307,17 +309,17 @@ export const StackManager = ({ children, ...props }: PropsWithChildren<StackMana
onStart,
onEnd,
};
};
}
const transitionPage = async (
async transitionPage(
routeInfo: RouteInfo,
enteringViewItem: ViewItem,
leavingViewItem: ViewItem,
leavingViewItem?: ViewItem,
direction?: 'forward' | 'back',
progressAnimation = false
) => {
) {
const runCommit = async (enteringEl: HTMLElement, leavingEl?: HTMLElement) => {
const skipTransition = skipTransitionRef.current;
const skipTransition = this.skipTransition;
/**
* If the transition was handled
@@ -340,99 +342,102 @@ export const StackManager = ({ children, ...props }: PropsWithChildren<StackMana
* transition triggered by handlePageTransition
* in componentDidUpdate.
*/
skipTransitionRef.current = false;
this.skipTransition = false;
} else {
enteringEl.classList.add('ion-page', 'ion-page-invisible');
enteringEl.classList.add('ion-page');
enteringEl.classList.add('ion-page-invisible');
}
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,
});
}
await routerOutlet.commit(enteringEl, leavingEl, {
duration: skipTransition || directionToUse === undefined ? 0 : undefined,
direction: directionToUse,
showGoBack: !!routeInfo.pushedByRoute,
progressAnimation,
animationBuilder: routeInfo.routeAnimation,
});
};
const routerInfoFallbackDirection =
const routerOutlet = this.routerOutletElement!;
const routeInfoFallbackDirection =
routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection;
const directionToUse = direction ?? routerInfoFallbackDirection;
const directionToUse = direction ?? routeInfoFallbackDirection;
const routerOutletElement = routerOutletRef.current;
if (enteringViewItem?.ionPageElement && routerOutletElement) {
if (leavingViewItem?.ionPageElement && enteringViewItem === leavingViewItem) {
if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) {
if (leavingViewItem && 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) {
routerOutletElement.appendChild(newLeavingElement);
this.routerOutletElement.appendChild(newLeavingElement);
await runCommit(enteringViewItem.ionPageElement, newLeavingElement);
routerOutletElement.removeChild(newLeavingElement);
this.routerOutletElement.removeChild(newLeavingElement);
}
} else {
await runCommit(enteringViewItem.ionPageElement, undefined);
}
} else {
await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
if (leavingViewItem?.ionPageElement && !progressAnimation) {
const { ionPageElement } = leavingViewItem;
ionPageElement.setAttribute('aria-hidden', 'true');
ionPageElement.classList.add('ion-page-hidden');
if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) {
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
}
}
}
};
}
const renderComponents = () => {
const ionRouterOutlet = React.Children.only<React.ReactElement>(children as React.ReactElement);
ionRouterOutletRef.current = ionRouterOutlet;
render() {
const { children } = this.props;
const ionRouterOutlet = React.Children.only(children) as React.ReactElement;
this.ionRouterOutlet = ionRouterOutlet;
const components = getChildrenToRender(id, ionRouterOutlet, routeInfo, () => {
forceUpdate();
const components = this.context.getChildrenToRender(this.id, this.ionRouterOutlet, this.props.routeInfo, () => {
this.forceUpdate();
});
return cloneElement(ionRouterOutlet, {
ref: (node: HTMLIonRouterOutletElement) => {
if (ionRouterOutlet.props.setRef) {
ionRouterOutlet.props.setRef(node);
}
if (ionRouterOutlet.props.forwardedRef) {
ionRouterOutlet.props.forwardedRef.current = node;
}
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>
);
}
routerOutletRef.current = node;
const { ref } = ionRouterOutlet as any;
if (typeof ref === 'function') {
ref(node);
}
},
components,
});
};
return <StackContext.Provider value={stackContextValue}>{renderComponents()}</StackContext.Provider>;
};
static get contextType() {
return RouteManagerContext;
}
}
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;
@@ -459,7 +464,6 @@ 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,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
### Bug Fixes
* **tab-button:** update event type interface on React ([#27950](https://github.com/ionic-team/ionic-framework/issues/27950)) ([1cf1eca](https://github.com/ionic-team/ionic-framework/commit/1cf1eca00239f3e98854466116e42f9a2e7b4590)), closes [#27949](https://github.com/ionic-team/ionic-framework/issues/27949)
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
**Note:** Version bump only for package @ionic/react
## [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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ type Props = LocalJSX.IonTabButton &
IonicReactProps & {
routerOptions?: RouterOptions;
ref?: React.Ref<HTMLIonTabButtonElement>;
onClick?: (e: Event) => void;
onClick?: (e: CustomEvent) => void;
};
export const IonTabButton = /*@__PURE__*/ (() =>

View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Ionic App</title>
<base href="/" />
<meta name="color-scheme" content="light dark" />
<meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<link rel="manifest" href="/manifest.json" />
<link rel="shortcut icon" type="image/png" href="/favicon.png" />
<!-- add to homescreen for ios -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Ionic App" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

View File

File diff suppressed because it is too large Load Diff

View File

@@ -3,59 +3,44 @@
"version": "0.0.1",
"private": true,
"dependencies": {
"@ionic/react": "^6.6.1",
"@ionic/react-router": "^6.6.1",
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
"ionicons": "^6.0.4",
"@ionic/react": "^7.0.0",
"@ionic/react-router": "^7.0.0",
"ionicons": "^7.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router": "^5.3.4",
"react-router-dom": "^5.3.4",
"react-scripts": "^5.0.0",
"typescript": "^4.1.3"
"react-router-dom": "^5.3.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom --transformIgnorePatterns 'node_modules/(?!(@ionic/react|@ionic/react-router|@ionic/core|@stencil/core|ionicons)/)'",
"eject": "react-scripts eject",
"dev": "vite",
"start": "vite",
"build": "tsc && vite build",
"test": "vitest",
"sync": "sh ./scripts/sync.sh",
"cypress": "cypress run --headless --browser chrome",
"cypress.open": "cypress open",
"e2e": "concurrently \"serve -s build -l 3000\" \"wait-on http-get://localhost:3000 && npm run cypress\" --kill-others --success first"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"e2e": "concurrently \"serve -s dist -l 3000\" \"wait-on http-get://localhost:3000 && npm run cypress\" --kill-others --success first"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
"@types/jest": "^27.4.1",
"@types/node": "^17.0.21",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
"@vitejs/plugin-legacy": "^4.0.2",
"@vitejs/plugin-react": "^4.0.1",
"concurrently": "^6.3.0",
"cypress": "^10.9.0",
"cypress": "^12.7.0",
"serve": "^14.0.1",
"wait-on": "^6.0.0",
"webpack-cli": "^4.9.1"
"eslint": "^8.35.0",
"eslint-plugin-react": "^7.32.2",
"jsdom": "^22.1.0",
"typescript": "^5.1.6",
"vite": "^4.3.9",
"vitest": "^0.32.2"
},
"description": "An Ionic project",
"engines": {

View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false,
"skipLibCheck": true,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "ESNext",
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

View File

@@ -0,0 +1,19 @@
import legacy from '@vitejs/plugin-legacy'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
legacy()
],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/setupTests.ts',
},
server: {
port: 3000
}
})

View File

@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
**Note:** Version bump only for package @ionic/vue-router
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
### Bug Fixes
* **react, vue:** custom animations are used when going back ([#27895](https://github.com/ionic-team/ionic-framework/issues/27895)) ([824033f](https://github.com/ionic-team/ionic-framework/commit/824033f1d4b4a3e5d4c6a978a39e5bb1f33b5bb4)), closes [#27873](https://github.com/ionic-team/ionic-framework/issues/27873)
## [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

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/vue-router",
"version": "7.2.1",
"version": "7.2.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/vue-router",
"version": "7.2.1",
"version": "7.2.3",
"license": "MIT",
"dependencies": {
"@ionic/vue": "^7.2.1"
"@ionic/vue": "^7.2.3"
},
"devDependencies": {
"@ionic/eslint-config": "^0.3.0",
@@ -660,9 +660,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.2.tgz",
"integrity": "sha512-KD+dZrrIpKs1BlE3Ffp05yrejSvurUhr0OWP2P/NRax5Dld8wSZj+ETOMkV8uiIPbOTOUbLaAQ7m5ZXduMCYhQ==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -871,11 +871,11 @@
}
},
"node_modules/@ionic/vue": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.2.1.tgz",
"integrity": "sha512-l9ucLk1NrAZHXdAT8qrJYArklS7cCok6/Qlq52Kg0+C912sfV3IIEMxjfQw8e6MX0aZGkSYMG/BGLEBqxp6gOw==",
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.2.2.tgz",
"integrity": "sha512-yRdwSg3GZokcIoBZ/ZxPl/ZDLyZsj6cisEg4XqtBAcX3d93rJtzxP4Hq+Ql7CN4hOhcpn1MX8BFLrCK/nSj94g==",
"dependencies": {
"@ionic/core": "7.2.1",
"@ionic/core": "7.2.2",
"ionicons": "^7.0.0"
}
},
@@ -7697,9 +7697,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.1.tgz",
"integrity": "sha512-7I3OIGHIhGCXxswphP3vfuyCCjXysAUg4cfKGB2QQ5e073DPsZkMSpVam19nAIjEDyfK2pQZC+SgSkZvvCc+2A==",
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.2.tgz",
"integrity": "sha512-KD+dZrrIpKs1BlE3Ffp05yrejSvurUhr0OWP2P/NRax5Dld8wSZj+ETOMkV8uiIPbOTOUbLaAQ7m5ZXduMCYhQ==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -7829,11 +7829,11 @@
"requires": {}
},
"@ionic/vue": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.2.1.tgz",
"integrity": "sha512-l9ucLk1NrAZHXdAT8qrJYArklS7cCok6/Qlq52Kg0+C912sfV3IIEMxjfQw8e6MX0aZGkSYMG/BGLEBqxp6gOw==",
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-7.2.2.tgz",
"integrity": "sha512-yRdwSg3GZokcIoBZ/ZxPl/ZDLyZsj6cisEg4XqtBAcX3d93rJtzxP4Hq+Ql7CN4hOhcpn1MX8BFLrCK/nSj94g==",
"requires": {
"@ionic/core": "7.2.1",
"@ionic/core": "7.2.2",
"ionicons": "^7.0.0"
}
},

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue-router",
"version": "7.2.1",
"version": "7.2.3",
"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.1"
"@ionic/vue": "^7.2.3"
},
"devDependencies": {
"@ionic/eslint-config": "^0.3.0",

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [7.2.3](https://github.com/ionic-team/ionic-framework/compare/v7.2.2...v7.2.3) (2023-08-09)
**Note:** Version bump only for package @ionic/vue
## [7.2.2](https://github.com/ionic-team/ionic-framework/compare/v7.2.1...v7.2.2) (2023-08-02)
**Note:** Version bump only for package @ionic/vue
## [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

View File

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

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/vue",
"version": "7.2.1",
"version": "7.2.3",
"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.1",
"@ionic/core": "^7.2.3",
"ionicons": "^7.0.0"
},
"vetur": {