Compare commits

..

49 Commits

Author SHA1 Message Date
Liam DeBeasi
a08a07cbab fix(datetime): clear button calls reset method 2023-08-14 09:13:30 -04:00
Liam DeBeasi
c2bcdcbec3 run build 2023-08-10 17:20:38 -05:00
Liam DeBeasi
f02aff87af clarity 2023-08-10 17:19:41 -05:00
Liam DeBeasi
2af3702b3e clarify behavior 2023-08-10 17:19:23 -05:00
Liam DeBeasi
783a653071 Update datetime.e2e.ts 2023-08-10 18:17:34 -04:00
Liam DeBeasi
954baf3bef Update datetime.e2e.ts 2023-08-10 18:17:24 -04:00
Liam DeBeasi
48b4cc36b8 add issue annotation 2023-08-10 17:14:50 -05:00
Liam DeBeasi
3cef069078 clean up test 2023-08-10 17:14:05 -05:00
Liam DeBeasi
82f8ca8dd1 fix(datetime): reset falls back to value if set 2023-08-10 17:10:14 -05:00
Liam DeBeasi
6f276099fc fix(datetime): cancel resets internal state of datetime 2023-08-10 17:09:57 -05:00
Liam DeBeasi
f295b35671 lint 2023-08-10 17:09:36 -05:00
Liam DeBeasi
1a9714783f test(datetime): add tests 2023-08-10 17:09:16 -05:00
Liam DeBeasi
84cf0f152f chore: remove old comment from playwright config (#27973)
Issue number: #

---------

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

Shawn noted that I had an outdated comment in the `playwright.config.ts`
file when working on
https://github.com/ionic-team/ionic-framework/pull/27961:
https://github.com/ionic-team/ionic-framework/pull/27961#pullrequestreview-1572374939
 
## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->

- Remove the old comment

## 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-10 19:20:08 +00:00
Liam DeBeasi
28bd4ba720 fix(tap-click): do not error in document-less environment (#27972)
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. -->

While working on getting our starter app tests running on CI, I ran into
the following error:

```
⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯
ReferenceError: document is not defined
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
 ❯ Module.startTapClick node_modules/@ionic/core/components/index9.js:133:15
    131|   };
    132|   const doc = document;
    133|   doc.addEventListener('ionGestureCaptured', cancelActive);
       |               ^
    134|   doc.addEventListener('touchstart', onTouchStart, true);
    135|   doc.addEventListener('touchcancel', onTouchEnd, true);
 ❯ node_modules/@ionic/core/components/ion-app.js:21:113

This error originated in "src/App.test.tsx" test file. It doesn't mean the error was thrown inside the file itself, but while it was running.
This error was caught after test environment was torn down. Make sure to cancel any running tasks before test finishes:
```

We are referencing `document` without any "document defined" checks.

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

- Tap Click is only enabled if the `document` is available since we set
event listeners on the document.

## 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-10 18:48:09 +00:00
Liam DeBeasi
b6f43e0e72 test(playwright): enable github reporter, test retries (#27961)
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. -->

The team would like to explore solutions for being informed of flaky
tests in a way that is not disruptive to our workflow. Currently, flaky
tests fail immediately which means we have to re-run them every time.
We'd like flaky tests to be automatically retried but also reported to
us so we can address them in a separate PR.

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

- Enables the Playwright GitHub reporter. This will report about flaky
tests on the PR if applicable as well as in the CI results.
- Enables test retries. Tests will be retried up to 2 times before
failing.
- Disables reporting slow tests in the GitHub reporter. Some of our
tests require gesture interaction which are inherently slow but
otherwise working as intended. We don't necessarily need to know about
these right now.
- Disables "maxFailures". Tests that can fail at most once are never
detected as flaky since they are never retried. As a result, we need to
disable this in order to have flaky tests be reported to us.

## 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-10 17:22:46 +00:00
Sean Perkins
87bc207dad chore(input): remove generated documentation for size attribute (#27951)
Issue number: Resolves #27945 

---------

<!-- 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 `ion-input` accepts a `size` attribute that implies that it will
effect the size of the input under certain rules. This is not the case.
The `size` attribute has no effect on the size of the `input` element
since Ionic sets the input width to 100%.

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

- Removes the documentation around the `size` attribute so that it will
be removed from the Ionic Docs
- Adds a task link to remove the `size` attribute in an upcoming major
release

## 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-10 15:33:57 +00:00
Brandy Carney
8c6dc29133 Merge release-7.2.3
Release 7.2.3
2023-08-09 13:35:00 -04:00
ionitron
3a263d14c3 chore(): update package lock files 2023-08-09 16:28:15 +00:00
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
Liam DeBeasi
824033f1d4 fix(react, vue): custom animations are used when going back (#27895)
Issue number: resolves #27873

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

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

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

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

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

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

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

- Custom animation is now passed to `handleNavigate`

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

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


## Other information

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

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

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

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

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

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

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

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

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


## Other information

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

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

There are a few issues with the modern radio syntax:

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

I also discovered an issue with item:

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

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

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


ae77002afd

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


0a9b7fb91d

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


a8a90e53b2,
412d1d54e7,
and
1d1179b69a

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


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


f10eff47a5

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

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

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


## Other information

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

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

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

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


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

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

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

---

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

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


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-27 16:23:13 +00:00
Liam DeBeasi
0b8f1bc7dd fix(item-options): use correct safe area padding (#27853)
Issue number: Internal

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

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

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

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

LTR:

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

RTL:

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

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

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


## Other information

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

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


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

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

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

---

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

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


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-26 13:11:41 +00:00
Liam DeBeasi
38626d9680 fix(modal): body background is reset with inline card modals (#27835)
Issue number: resolves #27830

---------

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

Card modals set the body background to `black` to match iOS. This color
should be removed once the final card modal has been closed. When modals
were updated to work inline, the code that removed the background color
was never updated to account for this. As a result, opening multiple
inline card modals never removes the background color because there are
always >1 modals in the DOM.

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

- The card modal now queries for _visible_ modals in the DOM to
determine if it should remove the background color.

## 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.11689879279.14e28634`
2023-07-20 19:13:02 +00:00
Liam DeBeasi
6e4919caff fix(item-sliding): buttons are not interactive on close (#27829)
Issue number: resolves #22722

---------

<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->

<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->

Item Sliding Options are not disabled until after a 600ms timeout. This
timeout exists to allow the close transition to complete. Without the
timeout, the item sliding options disappear without a transition. I
explored waiting for the `transitionend` event instead of the
`setTimeout`, but the bug persisted.

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

- If an item sliding is closing then we add a class to the host. This
class disables pointer events on all `ion-item-options` children so the
buttons cannot be accidentally tapped while closing. This class is then
removed after the 600ms timeout.

## Does this introduce a breaking change?

- [ ] Yes
- [x] No

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


## Other information

<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->
2023-07-20 18:27:45 +00:00
dependabot[bot]
5122ced9e1 chore(deps-dev): Bump @capacitor/core from 5.2.1 to 5.2.2 in /core (#27832)
Bumps [@capacitor/core](https://github.com/ionic-team/capacitor) from
5.2.1 to 5.2.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ionic-team/capacitor/releases"><code>@​capacitor/core</code>'s
releases</a>.</em></p>
<blockquote>
<h2>5.2.2</h2>
<h2><a
href="https://github.com/ionic-team/capacitor/compare/5.2.1...5.2.2">5.2.2</a>
(2023-07-19)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>add http method to prototype.open (<a
href="https://redirect.github.com/ionic-team/capacitor/issues/6740">#6740</a>)
(<a
href="1fd2d8762f">1fd2d87</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/ionic-team/capacitor/blob/main/CHANGELOG.md"><code>@​capacitor/core</code>'s
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/ionic-team/capacitor/compare/5.2.1...5.2.2">5.2.2</a>
(2023-07-19)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>add http method to prototype.open (<a
href="https://redirect.github.com/ionic-team/capacitor/issues/6740">#6740</a>)
(<a
href="1fd2d8762f">1fd2d87</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="f378edae9e"><code>f378eda</code></a>
Release 5.2.2</li>
<li><a
href="1fd2d8762f"><code>1fd2d87</code></a>
fix: add http method to prototype.open (<a
href="https://redirect.github.com/ionic-team/capacitor/issues/6740">#6740</a>)</li>
<li><a
href="67c7f6e7bd"><code>67c7f6e</code></a>
chore: Improve bot message for requesting reproductions (<a
href="https://redirect.github.com/ionic-team/capacitor/issues/6735">#6735</a>)</li>
<li>See full diff in <a
href="https://github.com/ionic-team/capacitor/compare/5.2.1...5.2.2">compare
view</a></li>
</ul>
</details>
<br />


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

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

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

---

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

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


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-20 15:40:09 +00:00
Liam DeBeasi
3c794d25d6 merge feature-7.2
Feature 7.2
2023-07-19 14:24:54 -04:00
Liam DeBeasi
9c799a6eea merge release-7.2
Release 7.2
2023-07-19 13:41:06 -04:00
ionitron
08cc3d93ae chore(): update package lock files 2023-07-19 16:29:26 +00:00
125 changed files with 7664 additions and 20211 deletions

View File

@@ -3,6 +3,51 @@
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)
### Bug Fixes
* **item-sliding:** buttons are not interactive on close ([#27829](https://github.com/ionic-team/ionic-framework/issues/27829)) ([6e4919c](https://github.com/ionic-team/ionic-framework/commit/6e4919caff90fc60988e5cc85ad7161844eb5b51)), closes [#22722](https://github.com/ionic-team/ionic-framework/issues/22722)
* **modal:** body background is reset with inline card modals ([#27835](https://github.com/ionic-team/ionic-framework/issues/27835)) ([38626d9](https://github.com/ionic-team/ionic-framework/commit/38626d96809d1c6be523ea62a4fac1dec73ee891)), closes [#27830](https://github.com/ionic-team/ionic-framework/issues/27830)
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)

View File

@@ -3,6 +3,49 @@
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)
### Bug Fixes
* **item-sliding:** buttons are not interactive on close ([#27829](https://github.com/ionic-team/ionic-framework/issues/27829)) ([6e4919c](https://github.com/ionic-team/ionic-framework/commit/6e4919caff90fc60988e5cc85ad7161844eb5b51)), closes [#22722](https://github.com/ionic-team/ionic-framework/issues/22722)
* **modal:** body background is reset with inline card modals ([#27835](https://github.com/ionic-team/ionic-framework/issues/27835)) ([38626d9](https://github.com/ionic-team/ionic-framework/commit/38626d96809d1c6be523ea62a4fac1dec73ee891)), closes [#27830](https://github.com/ionic-team/ionic-framework/issues/27830)
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)

66
core/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/core",
"version": "7.2.0",
"version": "7.2.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
"version": "7.2.0",
"version": "7.2.3",
"license": "MIT",
"dependencies": {
"@stencil/core": "^3.4.0",
@@ -15,19 +15,19 @@
},
"devDependencies": {
"@axe-core/playwright": "^4.7.3",
"@capacitor/core": "^5.2.1",
"@capacitor/core": "^5.2.2",
"@capacitor/haptics": "^5.0.6",
"@capacitor/keyboard": "^5.0.6",
"@capacitor/status-bar": "^5.0.6",
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@jest/core": "^27.5.1",
"@playwright/test": "^1.36.1",
"@playwright/test": "^1.36.2",
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/angular-output-target": "^0.7.1",
"@stencil/react-output-target": "^0.5.3",
"@stencil/sass": "^3.0.4",
"@stencil/sass": "^3.0.5",
"@stencil/vue-output-target": "^0.8.6",
"@types/jest": "^27.5.2",
"@types/node": "^14.6.0",
@@ -607,9 +607,9 @@
"dev": true
},
"node_modules/@capacitor/core": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.2.1.tgz",
"integrity": "sha512-v7nzTQZj9l99Sp0v8C7Zq8QX6Cg5ljq7ASneWk/Hc5nBR5LOj/k3a+yEx/RoclWtkxJfs89Y5k+KJTFFQ6cLoA==",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.2.2.tgz",
"integrity": "sha512-3jKECZC5+YD2rljMZm1e/K3AYyoxUmLDZCyofTPbRYPBSI0wJh5ZCkX+XIGzNM0o/Wokl3Voa1JB8xsLC0MPxA==",
"dev": true,
"dependencies": {
"tslib": "^2.1.0"
@@ -1541,13 +1541,13 @@
}
},
"node_modules/@playwright/test": {
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.1.tgz",
"integrity": "sha512-YK7yGWK0N3C2QInPU6iaf/L3N95dlGdbsezLya4n0ZCh3IL7VgPGxC6Gnznh9ApWdOmkJeleT2kMTcWPRZvzqg==",
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz",
"integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==",
"dev": true,
"dependencies": {
"@types/node": "*",
"playwright-core": "1.36.1"
"playwright-core": "1.36.2"
},
"bin": {
"playwright": "cli.js"
@@ -1655,10 +1655,14 @@
}
},
"node_modules/@stencil/sass": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.4.tgz",
"integrity": "sha512-k1dP0A2QBx62m250FATc1hErXxXs6Jnf4TBxdL1C/dc32Kzz2n5aCT4SodBz0ebT5WMnITauZyFqYxzCzDoKag==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.5.tgz",
"integrity": "sha512-9nyllMXOEvHywo6fP2iwXgnq32A+OOUE36Aq7iUjzwT3wdr04qsvupO1JNIyRvmvCDF15hOKXztrZH1/wDu2Zg==",
"dev": true,
"engines": {
"node": ">=12.0.0",
"npm": ">=6.0.0"
},
"peerDependencies": {
"@stencil/core": ">=2.0.0 || >=3.0.0-beta.0 || >= 4.0.0-beta.0 || >= 4.0.0"
}
@@ -8190,9 +8194,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.1.tgz",
"integrity": "sha512-7+tmPuMcEW4xeCL9cp9KxmYpQYHKkyjwoXRnoeTowaeNat8PoBMk/HwCYhqkH2fRkshfKEOiVus/IhID2Pg8kg==",
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz",
"integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@@ -10784,9 +10788,9 @@
"dev": true
},
"@capacitor/core": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.2.1.tgz",
"integrity": "sha512-v7nzTQZj9l99Sp0v8C7Zq8QX6Cg5ljq7ASneWk/Hc5nBR5LOj/k3a+yEx/RoclWtkxJfs89Y5k+KJTFFQ6cLoA==",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@capacitor/core/-/core-5.2.2.tgz",
"integrity": "sha512-3jKECZC5+YD2rljMZm1e/K3AYyoxUmLDZCyofTPbRYPBSI0wJh5ZCkX+XIGzNM0o/Wokl3Voa1JB8xsLC0MPxA==",
"dev": true,
"requires": {
"tslib": "^2.1.0"
@@ -11451,14 +11455,14 @@
}
},
"@playwright/test": {
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.1.tgz",
"integrity": "sha512-YK7yGWK0N3C2QInPU6iaf/L3N95dlGdbsezLya4n0ZCh3IL7VgPGxC6Gnznh9ApWdOmkJeleT2kMTcWPRZvzqg==",
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz",
"integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==",
"dev": true,
"requires": {
"@types/node": "*",
"fsevents": "2.3.2",
"playwright-core": "1.36.1"
"playwright-core": "1.36.2"
}
},
"@rollup/plugin-node-resolve": {
@@ -11532,9 +11536,9 @@
"requires": {}
},
"@stencil/sass": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.4.tgz",
"integrity": "sha512-k1dP0A2QBx62m250FATc1hErXxXs6Jnf4TBxdL1C/dc32Kzz2n5aCT4SodBz0ebT5WMnITauZyFqYxzCzDoKag==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@stencil/sass/-/sass-3.0.5.tgz",
"integrity": "sha512-9nyllMXOEvHywo6fP2iwXgnq32A+OOUE36Aq7iUjzwT3wdr04qsvupO1JNIyRvmvCDF15hOKXztrZH1/wDu2Zg==",
"dev": true,
"requires": {}
},
@@ -16328,9 +16332,9 @@
}
},
"playwright-core": {
"version": "1.36.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.1.tgz",
"integrity": "sha512-7+tmPuMcEW4xeCL9cp9KxmYpQYHKkyjwoXRnoeTowaeNat8PoBMk/HwCYhqkH2fRkshfKEOiVus/IhID2Pg8kg==",
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz",
"integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==",
"dev": true
},
"postcss": {

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "7.2.0",
"version": "7.2.3",
"description": "Base components for Ionic",
"keywords": [
"ionic",
@@ -37,19 +37,19 @@
},
"devDependencies": {
"@axe-core/playwright": "^4.7.3",
"@capacitor/core": "^5.2.1",
"@capacitor/core": "^5.2.2",
"@capacitor/haptics": "^5.0.6",
"@capacitor/keyboard": "^5.0.6",
"@capacitor/status-bar": "^5.0.6",
"@ionic/eslint-config": "^0.3.0",
"@ionic/prettier-config": "^2.0.0",
"@jest/core": "^27.5.1",
"@playwright/test": "^1.36.1",
"@playwright/test": "^1.36.2",
"@rollup/plugin-node-resolve": "^8.4.0",
"@rollup/plugin-virtual": "^2.0.3",
"@stencil/angular-output-target": "^0.7.1",
"@stencil/react-output-target": "^0.5.3",
"@stencil/sass": "^3.0.4",
"@stencil/sass": "^3.0.5",
"@stencil/vue-output-target": "^0.8.6",
"@types/jest": "^27.5.2",
"@types/node": "^14.6.0",

View File

@@ -62,14 +62,16 @@ const config: PlaywrightTestConfig = {
},
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Fail fast on CI */
maxFailures: process.env.CI ? 1 : 0,
/* Flaky test should be either addressed or disabled until we can address them */
retries: 0,
maxFailures: 0,
retries: 2,
reportSlowTests: null,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
reporter: [
['html'],
['github']
],
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */

View File

@@ -819,7 +819,7 @@ export namespace Components {
}
interface IonDatetime {
/**
* Emits the ionCancel event and optionally closes the popover or modal that the datetime was presented in.
* The cancel method performs the following actions: 1. Emits the ionCancel event 2. Resets the internal state of the datetime 3. Closes the parent popover or modal if "closeOverlay" is true.
*/
"cancel": (closeOverlay?: boolean) => Promise<void>;
/**
@@ -915,15 +915,15 @@ export namespace Components {
*/
"readonly": boolean;
/**
* Resets the internal state of the datetime but does not update the value. Passing a valid ISO-8601 string will reset the state of the component to the provided date. If no value is provided, the internal state will be reset to the clamped value of the min, max and today.
* Resets the internal state of the datetime but does not update the value property. Passing a valid ISO-8601 string will reset the state of the component to the provided date. If no date string was passed but the value property is set, then the internal state of datetime will be reset to that value. Otherwise, the internal state will be reset to the clamped value of the min, max and today.
*/
"reset": (startDate?: string) => Promise<void>;
/**
* If `true`, a "Clear" button will be rendered alongside the default "Cancel" and "OK" buttons at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered.
* If `true`, a "Clear" button will be rendered alongside the default "Cancel" and "OK" buttons at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered. Pressing the "Clear" button will call the "reset" method.
*/
"showClearButton": boolean;
/**
* If `true`, the default "Cancel" and "OK" buttons will be rendered at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered.
* If `true`, the default "Cancel" and "OK" buttons will be rendered at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered. Pressing the "Cancel" button will call the "cancel" method. Pressing the "OK" button will the "confirm" method.
*/
"showDefaultButtons": boolean;
/**
@@ -1277,9 +1277,6 @@ export namespace Components {
* The shape of the input. If "round" it will have an increased border radius.
*/
"shape"?: 'round';
/**
* The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored.
*/
"size"?: number;
/**
* If `true`, the element will have its spelling and grammar checked.
@@ -2176,7 +2173,7 @@ export namespace Components {
*/
"side": PositionSide;
/**
* Describes how to calculate the popover width. If `"cover"`, the popover width will match the width of the trigger. If `"auto"`, the popover width will be determined by the content in the popover.
* Describes how to calculate the popover width. If `"cover"`, the popover width will match the width of the trigger. If `"auto"`, the popover width will be set to a static default value.
*/
"size": PopoverSize;
/**
@@ -4957,11 +4954,11 @@ declare namespace LocalJSX {
*/
"readonly"?: boolean;
/**
* If `true`, a "Clear" button will be rendered alongside the default "Cancel" and "OK" buttons at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered.
* If `true`, a "Clear" button will be rendered alongside the default "Cancel" and "OK" buttons at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered. Pressing the "Clear" button will call the "reset" method.
*/
"showClearButton"?: boolean;
/**
* If `true`, the default "Cancel" and "OK" buttons will be rendered at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered.
* If `true`, the default "Cancel" and "OK" buttons will be rendered at the bottom of the `ion-datetime` component. Developers can also use the `button` slot if they want to customize these buttons. If custom buttons are set in the `button` slot then the default buttons will not be rendered. Pressing the "Cancel" button will call the "cancel" method. Pressing the "OK" button will the "confirm" method.
*/
"showDefaultButtons"?: boolean;
/**
@@ -5339,9 +5336,6 @@ declare namespace LocalJSX {
* The shape of the input. If "round" it will have an increased border radius.
*/
"shape"?: 'round';
/**
* The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored.
*/
"size"?: number;
/**
* If `true`, the element will have its spelling and grammar checked.
@@ -6189,7 +6183,7 @@ declare namespace LocalJSX {
*/
"side"?: PositionSide;
/**
* Describes how to calculate the popover width. If `"cover"`, the popover width will match the width of the trigger. If `"auto"`, the popover width will be determined by the content in the popover.
* Describes how to calculate the popover width. If `"cover"`, the popover width will match the width of the trigger. If `"auto"`, the popover width will be set to a static default value.
*/
"size"?: PopoverSize;
/**

View File

@@ -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;
/**
@@ -425,6 +424,9 @@ export class Datetime implements ComponentInterface {
* if they want to customize these buttons. If custom
* buttons are set in the `button` slot then the
* default buttons will not be rendered.
*
* Pressing the "Cancel" button will call the "cancel" method.
* Pressing the "OK" button will the "confirm" method.
*/
@Prop() showDefaultButtons = false;
@@ -435,6 +437,8 @@ export class Datetime implements ComponentInterface {
* if they want to customize these buttons. If custom
* buttons are set in the `button` slot then the
* default buttons will not be rendered.
*
* Pressing the "Clear" button will call the "reset" method.
*/
@Prop() showClearButton = false;
@@ -541,25 +545,29 @@ export class Datetime implements ComponentInterface {
}
/**
* Resets the internal state of the datetime but does not update the value.
* Resets the internal state of the datetime but does not update the value property.
* Passing a valid ISO-8601 string will reset the state of the component to the provided date.
* If no value is provided, the internal state will be reset to the clamped value of the min, max and today.
* If no date string was passed but the value property is set, then the internal state of
* datetime will be reset to that value. Otherwise, the internal state will be reset to the
* clamped value of the min, max and today.
*/
@Method()
async reset(startDate?: string) {
this.processValue(startDate);
this.processValue(startDate ?? this.value);
}
/**
* Emits the ionCancel event and
* optionally closes the popover
* or modal that the datetime was
* presented in.
* The cancel method performs the following actions:
* 1. Emits the ionCancel event
* 2. Resets the internal state of the datetime
* 3. Closes the parent popover or modal if "closeOverlay" is true.
*/
@Method()
async cancel(closeOverlay = false) {
this.ionCancel.emit();
this.reset();
if (closeOverlay) {
this.closeParentOverlay();
}
@@ -883,21 +891,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
@@ -1380,11 +1385,6 @@ export class Datetime implements ComponentInterface {
return;
}
const clearButtonClick = () => {
this.reset();
this.setValue(undefined);
};
/**
* By default we render two buttons:
* Cancel - Dismisses the datetime and
@@ -1410,7 +1410,7 @@ export class Datetime implements ComponentInterface {
)}
<div>
{showClearButton && (
<ion-button id="clear-button" color={this.color} onClick={() => clearButtonClick()}>
<ion-button id="clear-button" color={this.color} onClick={() => this.reset()}>
{this.clearText}
</ion-button>
)}
@@ -2364,19 +2364,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 +2384,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

@@ -380,40 +380,6 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
});
});
/**
* This behavior does not differ across
* modes/directions.
*/
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('datetime: clear button'), () => {
test('should clear the active calendar day', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/26258',
});
await page.setContent(
`
<ion-datetime value="2022-11-10" show-clear-button="true"></ion-datetime>
`,
config
);
await page.waitForSelector('.datetime-ready');
const selectedDay = page.locator('ion-datetime .calendar-day-active');
await expect(selectedDay).toHaveText('10');
await page.click('ion-datetime #clear-button');
await page.waitForChanges();
await expect(selectedDay).toHaveCount(0);
});
});
});
/**
* This behavior does not differ across
* modes/directions.

View File

@@ -0,0 +1,75 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('datetime: cancel method'), () => {
test('should emit ionCancel', async ({ page }) => {
await page.setContent(
`
<ion-datetime></ion-datetime>
`,
config
);
const ionCancel = await page.spyOnEvent('ionCancel');
const datetime = page.locator('ion-datetime');
await datetime.evaluate((el: HTMLIonDatetimeElement) => el.cancel());
await ionCancel.next();
});
test('parent overlay should be dismissed when true is passed', async ({ page }) => {
await page.setContent(
`
<ion-modal>
<ion-datetime></ion-datetime>
</ion-modal>
`,
config
);
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
const datetime = page.locator('ion-datetime');
const modal = page.locator('ion-modal');
await modal.evaluate((el: HTMLIonModalElement) => el.present());
await ionModalDidPresent.next();
await datetime.evaluate((el: HTMLIonDatetimeElement) => el.cancel(true));
await ionModalDidDismiss.next();
});
test('should reset the internal state of datetime', async ({ page }, testInfo) => {
testInfo.annotations.push({
type: 'issue',
description: 'https://github.com/ionic-team/ionic-framework/issues/27975',
});
await page.setContent(
`
<ion-datetime value="2023-06-06T16:30" show-default-buttons="true"></ion-datetime>
`,
config
);
const datetime = page.locator('ion-datetime');
const dayOne = datetime.locator('.calendar-day[data-month="6"][data-day="1"][data-year="2023"]');
const daySix = datetime.locator('.calendar-day[data-month="6"][data-day="6"][data-year="2023"]');
await dayOne.click();
await page.waitForChanges();
await expect(dayOne).toHaveClass(/calendar-day-active/);
await datetime.evaluate((el: HTMLIonDatetimeElement) => el.cancel());
await page.waitForChanges();
await expect(daySix).toHaveClass(/calendar-day-active/);
});
});
});

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

@@ -0,0 +1,100 @@
import { expect } from '@playwright/test';
import { configs, test } from '@utils/test/playwright';
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
test.describe(title('datetime: reset method'), () => {
test('should reset the internal state of datetime to the set value', async ({ page }) => {
await page.setContent(
`
<ion-datetime value="2023-06-06T16:30" show-default-buttons="true"></ion-datetime>
`,
config
);
const datetime = page.locator('ion-datetime');
const dayOne = datetime.locator('.calendar-day[data-month="6"][data-day="1"][data-year="2023"]');
const daySix = datetime.locator('.calendar-day[data-month="6"][data-day="6"][data-year="2023"]');
await dayOne.click();
await page.waitForChanges();
await expect(dayOne).toHaveClass(/calendar-day-active/);
await datetime.evaluate((el: HTMLIonDatetimeElement) => el.reset());
await page.waitForChanges();
await expect(daySix).toHaveClass(/calendar-day-active/);
});
test('should reset the internal state of datetime to the provided value', async ({ page }) => {
await page.setContent(
`
<ion-datetime show-default-buttons="true"></ion-datetime>
<script>
const mockToday = '2023-06-12T16:22';
Date = class extends Date {
constructor(...args) {
if (args.length === 0) {
super(mockToday)
} else {
super(...args);
}
}
}
</script>
`,
config
);
const datetime = page.locator('ion-datetime');
const dayOne = datetime.locator('.calendar-day[data-month="6"][data-day="1"][data-year="2023"]');
const daySix = datetime.locator('.calendar-day[data-month="6"][data-day="6"][data-year="2023"]');
await dayOne.click();
await page.waitForChanges();
await expect(dayOne).toHaveClass(/calendar-day-active/);
await datetime.evaluate((el: HTMLIonDatetimeElement) => el.reset('2023-06-06T16:30'));
await page.waitForChanges();
await expect(daySix).toHaveClass(/calendar-day-active/);
});
test('should reset the internal state of datetime to today when value is set or passed', async ({ page }) => {
await page.setContent(
`
<ion-datetime show-default-buttons="true"></ion-datetime>
<script>
const mockToday = '2023-06-06T16:22';
Date = class extends Date {
constructor(...args) {
if (args.length === 0) {
super(mockToday)
} else {
super(...args);
}
}
}
</script>
`,
config
);
const datetime = page.locator('ion-datetime');
const dayOne = datetime.locator('.calendar-day[data-month="6"][data-day="1"][data-year="2023"]');
await dayOne.click();
await page.waitForChanges();
await expect(dayOne).toHaveClass(/calendar-day-active/);
await datetime.evaluate((el: HTMLIonDatetimeElement) => el.reset());
await page.waitForChanges();
await expect(await datetime.locator('.calendar-day-active').count()).toBe(0);
});
});
});

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

@@ -268,9 +268,7 @@ export class Input implements ComponentInterface {
*/
@Prop() step?: string;
/**
* The initial size of the control. This value is in pixels unless the value of the type attribute is `"text"` or `"password"`, in which case it is an integer number of characters. This attribute applies only when the `type` attribute is set to `"text"`, `"search"`, `"tel"`, `"url"`, `"email"`, or `"password"`, otherwise it is ignored.
*/
// FW-4914 Remove this property in Ionic 8
@Prop() size?: number;
/**

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

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

View File

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

View File

@@ -29,6 +29,9 @@ ion-item-sliding .item {
will-change: transform;
}
.item-sliding-closing ion-item-options {
pointer-events: none;
}
.item-sliding-active-swipe-end .item-options-end .item-option-expandable {
@include multi-dir() {

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',
@@ -407,6 +414,9 @@ export class ItemSliding implements ComponentInterface {
if (!this.item) {
return;
}
const { el } = this;
const style = this.item.style;
this.openAmount = openAmount;
@@ -425,6 +435,12 @@ export class ItemSliding implements ComponentInterface {
? SlidingState.Start | SlidingState.SwipeStart
: SlidingState.Start;
} else {
/**
* The sliding options should not be
* clickable while the item is closing.
*/
el.classList.add('item-sliding-closing');
/**
* Item sliding cannot be interrupted
* while closing the item. If it did,
@@ -441,6 +457,7 @@ export class ItemSliding implements ComponentInterface {
if (this.gesture) {
this.gesture.enable(!this.disabled);
}
el.classList.remove('item-sliding-closing');
}, 600);
openSlidingItem = undefined;

View File

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

View File

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

View File

@@ -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

@@ -51,9 +51,9 @@ export const iosLeaveAnimation = (baseEl: HTMLElement, opts: ModalAnimationOptio
presentingEl.style.setProperty('overflow', '');
const numModals = Array.from(bodyEl.querySelectorAll('ion-modal')).filter(
(m) => m.presentingElement !== undefined
).length;
const numModals = (
Array.from(bodyEl.querySelectorAll('ion-modal:not(.overlay-hidden)')) as HTMLIonModalElement[]
).filter((m) => m.presentingElement !== undefined).length;
if (numModals <= 1) {
bodyEl.style.setProperty('background-color', '');
}

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

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

View File

@@ -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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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

@@ -1,7 +1,13 @@
import { doc } from '@utils/browser';
import type { Config } from '../../interface';
import { now, pointerCoord } from '../helpers';
export const startTapClick = (config: Config) => {
if (doc === undefined) {
return;
}
let lastTouch = -MOUSE_WAIT * 10;
let lastActivated = 0;
@@ -143,7 +149,6 @@ export const startTapClick = (config: Config) => {
}
};
const doc = document;
doc.addEventListener('ionGestureCaptured', cancelActive);
doc.addEventListener('touchstart', onTouchStart, true);

View File

@@ -3,6 +3,30 @@
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
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)
**Note:** Version bump only for package @ionic/docs

View File

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

View File

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

View File

@@ -3,6 +3,30 @@
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
# [7.2.0](https://github.com/ionic-team/ionic-framework/compare/v7.1.4...v7.2.0) (2023-07-19)
**Note:** Version bump only for package @ionic/angular-server

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular-server",
"version": "7.2.0",
"version": "7.2.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular-server",
"version": "7.2.0",
"version": "7.2.3",
"license": "MIT",
"dependencies": {
"@ionic/core": "^7.2.0"
"@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.1.4",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.1.4.tgz",
"integrity": "sha512-/h32Sc7Jd8csdMZ6BhddoTGC/X7cO0tIOeDuyC/ypMarXajScHc0pgVEBneAA7PVBEVxsoeMDP7yuqJt6Duaiw==",
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.3.tgz",
"integrity": "sha512-9ms4JTozhoFzOlgVRozuRcXiyW5YDNlPqJYZx6xVPYPfcRUTIANQ9PZbU/Xqb9RA1xucTSgbKy91nt/hzJIalg==",
"dependencies": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",
@@ -7342,9 +7342,9 @@
"dev": true
},
"@ionic/core": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.1.4.tgz",
"integrity": "sha512-/h32Sc7Jd8csdMZ6BhddoTGC/X7cO0tIOeDuyC/ypMarXajScHc0pgVEBneAA7PVBEVxsoeMDP7yuqJt6Duaiw==",
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-7.2.3.tgz",
"integrity": "sha512-9ms4JTozhoFzOlgVRozuRcXiyW5YDNlPqJYZx6xVPYPfcRUTIANQ9PZbU/Xqb9RA1xucTSgbKy91nt/hzJIalg==",
"requires": {
"@stencil/core": "^3.4.0",
"ionicons": "7.1.0",

View File

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

Some files were not shown because too many files have changed in this diff Show More