Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(footer, tab-bar): wait for resize before re-showing #27417

Merged
merged 18 commits into from
May 16, 2023
Merged

Conversation

liamdebeasi
Copy link
Contributor

@liamdebeasi liamdebeasi commented May 8, 2023

Issue number: resolves #25990


What is the current behavior?

The tab bar and footer are being shown too soon after the keyboard begins to hide. This is happening because the webview resizes after the keyboard begins to dismiss. As a result, it is possible for the tab bar and footer to briefly appear on the top of the keyboard in environments where the webview resizes.

What is the new behavior?

  • The tab bar and footer wait until after the webview has resized before showing again
before after
RPReplay_Final1683569175.MP4
RPReplay_Final1683570992.MP4

This code works by adding an optional parameter to the keyboard controller callback called waitForResize. When defined, code within Ionic can wait for the webview to resize as a result of the keyboard opening or closing. Tab bar and footer wait for this waitForResize promise to resolve before re-showing the relevant elements.

This waitForResize parameter is only only defined when all of the following are two:

1. The webview resize mode is known and is not "None".

If the webview resize mode is unknown then either the Keyboard plugin is not installed (in which case the tab bar/footer are never hidden in the first place) or the app is being deployed in a browser/PWA environment (in which case the web content typically does not resize). If the webview resize mode is "None" then that means the keyboard plugin is installed, but the webview is configured to never resize when the keyboard opens/closes. As a result, there is no need to wait for the webview to resize.

2. The webview has previously resized.

If the keyboard is closed before the opening keyboard animation completes then it is possible for the webview to never resize. In this case, the webview is at full height and the tab bar/footer can immediately be re-shown.


Under the hood, we use a ResizeObserver to listen for when the web content resizes. Which element we listen on depends on the resize mode set in the developer's Capacitor app. We determine this in the getResizeContainer function.

From there, we wait for the ResizeObserver callback, then wait 1 more frame so the promise resolves after the resize has finished.

Does this introduce a breaking change?

  • Yes
  • No

Other information

Dev build: 7.0.6-dev.11683905366.13943af0

@stackblitz
Copy link

stackblitz bot commented May 8, 2023

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@github-actions github-actions bot added the package: core @ionic/core package label May 8, 2023
@liamdebeasi liamdebeasi changed the title Fw 2314 fix(footer, tab-bar): wait for resize before re-showing May 8, 2023
import { raf } from '@utils/helpers';
import { win } from '@utils/window';

import { KeyboardResize, Keyboard } from '../native/keyboard';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gave me a strange build error when I tried to do @utils/native/keyboard:

[ ERROR ]  Rollup: Unresolved Import
           Could not resolve '../native/keyboa' from ./src/utils/keyboard/keyboard-controller.ts

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this resolved? I wasn't able to reproduce any build errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still getting the issue locally when doing npm run start:

[ ERROR ]  Rollup: Unresolved Import
           Could not resolve '../native/keyboa' from
           ./src/utils/keyboard/keyboard-controller.ts

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also not able to reproduce any issues when running npm run start. Does it happen as soon as the command is entered or does it show when on a specific page?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm getting the error as soon as I run npm start, or at least a bit later once the build has finished. I did also try rm ./dist and npm install in case I had some build cache or oudated Stencil issues, but the error's still occuring. Odd 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can ask the Stencil team about this during our sync today, but I don't think it's worth holding up this PR over.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super weird, I'll be curious to know the outcome.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spoke with Ryan, and he thinks the compiler may be expecting to see a ts extension since only 2 characters are being cut off (i.e. the length of the file extension). Tanner is working on a similar bug, and Ryan had me try dev build with Tanner's proposed fix. However, the proposed fix did not change the behavior here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For posterity, the fix for this is tied to ionic-team/stencil#4481

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix landed/released in Stencil v3.4.1 - https://github.com/ionic-team/stencil/releases/tag/v3.4.1

@liamdebeasi liamdebeasi marked this pull request as ready for review May 15, 2023 13:21
@liamdebeasi liamdebeasi requested a review from a team as a code owner May 15, 2023 13:21
@thetaPC thetaPC requested a review from a team May 16, 2023 16:34
Copy link
Contributor

@averyjohnston averyjohnston left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM once Maria's comment is addressed 👍

@liamdebeasi liamdebeasi requested a review from thetaPC May 16, 2023 17:31
Copy link
Contributor

@thetaPC thetaPC left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@liamdebeasi liamdebeasi added this pull request to the merge queue May 16, 2023
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks May 16, 2023
@liamdebeasi liamdebeasi added this pull request to the merge queue May 16, 2023
Merged via the queue into main with commit 70d9854 May 16, 2023
45 checks passed
@liamdebeasi liamdebeasi deleted the FW-2314 branch May 16, 2023 19:56
brandyscarney pushed a commit that referenced this pull request May 22, 2023
Issue number: resolves #25990

---------

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

<!-- Please describe the current behavior that you are modifying. -->

The tab bar and footer are being shown too soon after the keyboard
begins to hide. This is happening because the webview resizes _after_
the keyboard begins to dismiss. As a result, it is possible for the tab
bar and footer to briefly appear on the top of the keyboard in
environments where the webview resizes.

<!-- Please describe the behavior or changes that are being added by
this PR. -->

- The tab bar and footer wait until after the webview has resized before
showing again

| before | after |
| - | - |
| <video
src="https://user-images.githubusercontent.com/2721089/236905066-42ac17a5-a5bf-458b-9c62-005fcce05e20.MP4"></video>
| <video
src="https://user-images.githubusercontent.com/2721089/236905185-d2f539d1-6d93-4385-b1cb-24dd7aa06393.MP4"></video>
|

This code works by adding an optional parameter to the keyboard
controller callback called `waitForResize`. When defined, code within
Ionic can wait for the webview to resize as a result of the keyboard
opening or closing. Tab bar and footer wait for this `waitForResize`
promise to resolve before re-showing the relevant elements.

This `waitForResize` parameter is only only defined when all of the
following are two:

**1. The webview resize mode is known and is _not_ "None".**

If the webview resize mode is unknown then either the Keyboard plugin is
not installed (in which case the tab bar/footer are never hidden in the
first place) or the app is being deployed in a browser/PWA environment
(in which case the web content typically does not resize). If the
webview resize mode is "None" then that means the keyboard plugin is
installed, but the webview is configured to never resize when the
keyboard opens/closes. As a result, there is no need to wait for the
webview to resize.

**2. The webview has previously resized.**

If the keyboard is closed _before_ the opening keyboard animation
completes then it is possible for the webview to never resize. In this
case, the webview is at full height and the tab bar/footer can
immediately be re-shown.

------

Under the hood, we use a
[ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
to listen for when the web content resizes. Which element we listen on
depends on the resize mode set in the developer's Capacitor app. We
determine this in the `getResizeContainer` function.

From there, we wait for the ResizeObserver callback, then wait 1 more
frame so the promise resolves _after_ the resize has finished.

- [ ] Yes
- [x] No

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

<!-- 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.0.6-dev.11683905366.13943af0`
alicewriteswrongs added a commit to ionic-team/stencil that referenced this pull request Jun 13, 2023
This fixes an issue (STENCIL-840) which is mentioned here:
ionic-team/ionic-framework#27417 (comment)

The problem is that for _certain_ aliased paths more of the resolve
filepath is getting cut off than intended. In the example referenced in
the discussion above, for instance, the resolved path should look
something like `../native/keyboard` but instead it looks like
`../native/keyboa`.

In `rewriteAliasedSourceFileImportPaths` we rewrite aliased paths by
basically:

1. resolving the full path to the aliased module (so e.g. `@utils` is
   resolved to `src/utils/index.ts`)
2. calculating the relative path from the importing module (where there
   is an import like `import { foo } from '@utils';`). This might look
   something like `../../utils/index.ts`.
3. re-writing the resolved relative path to remove the file extension,
   giving us something like `../../utils/index`.

The problem arose in the third step. In order to get complete coverage
for all the possible file extensions we could run into we
programmatically construct a regular expression based on
`ts.Extension`. On `main` at present this looks like this:

```ts
  const extensionRegex = new RegExp(
    Object.values(ts.Extension)
      .map((extension) => `${extension}$`)
      .join('|')
  );
```

The problem is that `Object.values(ts.Extension)` looks like this:

```ts
[
  '.ts',
  '.tsx',
  '.d.ts',
  '.js',
  '.jsx',
  '.json',
  '.tsbuildinfo',
  '.mjs',
  '.mts',
  '.d.mts',
  '.cjs',
  '.cts',
  '.d.cts'
]
```

Thus our regular expression will end up having patterns in it like

```ts
/.d.ts$/
```

This will match the substring `rd.ts` at the end of a string like
`../native/keyboard.ts`, because `.` is the wildcard and will match any
character. This is not what we want!

The solution is to replace `"."` in the extensions with `"\\."` to match
only the string `"."`, which was the original intention.
alicewriteswrongs added a commit to ionic-team/stencil that referenced this pull request Jun 13, 2023
This fixes an issue (STENCIL-840) which is mentioned here:
ionic-team/ionic-framework#27417 (comment)

The problem is that for _certain_ aliased paths more of the resolve
filepath is getting cut off than intended. In the example referenced in
the discussion above, for instance, the resolved path should look
something like `../native/keyboard` but instead it looks like
`../native/keyboa`.

In `rewriteAliasedSourceFileImportPaths` we rewrite aliased paths by
basically:

1. resolving the full path to the aliased module (so e.g. `@utils` is
   resolved to `src/utils/index.ts`)
2. calculating the relative path from the importing module (where there
   is an import like `import { foo } from '@utils';`). This might look
   something like `../../utils/index.ts`.
3. re-writing the resolved relative path to remove the file extension,
   giving us something like `../../utils/index`.

The problem arose in the third step. In order to get complete coverage
for all the possible file extensions we could run into we
programmatically construct a regular expression based on
`ts.Extension`. On `main` at present this looks like this:

```ts
  const extensionRegex = new RegExp(
    Object.values(ts.Extension)
      .map((extension) => `${extension}$`)
      .join('|')
  );
```

The problem is that `Object.values(ts.Extension)` looks like this:

```ts
[
  '.ts',
  '.tsx',
  '.d.ts',
  '.js',
  '.jsx',
  '.json',
  '.tsbuildinfo',
  '.mjs',
  '.mts',
  '.d.mts',
  '.cjs',
  '.cts',
  '.d.cts'
]
```

Thus our regular expression will end up having patterns in it like

```ts
/.d.ts$/
```

This will match the substring `rd.ts` at the end of a string like
`../native/keyboard.ts`, because `.` is the wildcard and will match any
character. This is not what we want!

The solution is to replace `"."` in the extensions with `"\\."` to match
only the string `"."`, which was the original intention.
alicewriteswrongs added a commit to ionic-team/stencil that referenced this pull request Jun 22, 2023
This fixes an issue (STENCIL-840) which is mentioned here:
ionic-team/ionic-framework#27417 (comment)

The problem is that for _certain_ aliased paths more of the resolve
filepath is getting cut off than intended. In the example referenced in
the discussion above, for instance, the resolved path should look
something like `../native/keyboard` but instead it looks like
`../native/keyboa`.

In `rewriteAliasedSourceFileImportPaths` we rewrite aliased paths by
basically:

1. resolving the full path to the aliased module (so e.g. `@utils` is
   resolved to `src/utils/index.ts`)
2. calculating the relative path from the importing module (where there
   is an import like `import { foo } from '@utils';`). This might look
   something like `../../utils/index.ts`.
3. re-writing the resolved relative path to remove the file extension,
   giving us something like `../../utils/index`.

The problem arose in the third step. In order to get complete coverage
for all the possible file extensions we could run into we
programmatically construct a regular expression based on
`ts.Extension`. On `main` at present this looks like this:

```ts
  const extensionRegex = new RegExp(
    Object.values(ts.Extension)
      .map((extension) => `${extension}$`)
      .join('|')
  );
```

The problem is that `Object.values(ts.Extension)` looks like this:

```ts
[
  '.ts',
  '.tsx',
  '.d.ts',
  '.js',
  '.jsx',
  '.json',
  '.tsbuildinfo',
  '.mjs',
  '.mts',
  '.d.mts',
  '.cjs',
  '.cts',
  '.d.cts'
]
```

Thus our regular expression will end up having patterns in it like

```ts
/.d.ts$/
```

This will match the substring `rd.ts` at the end of a string like
`../native/keyboard.ts`, because `.` is the wildcard and will match any
character. This is not what we want!

The solution is to replace `"."` in the extensions with `"\\."` to match
only the string `"."`, which was the original intention.
alicewriteswrongs added a commit to ionic-team/stencil that referenced this pull request Jun 22, 2023
This fixes an issue (STENCIL-840) which is mentioned here:
ionic-team/ionic-framework#27417 (comment)

The problem is that for _certain_ aliased paths more of the resolve
filepath is getting cut off than intended. In the example referenced in
the discussion above, for instance, the resolved path should look
something like `../native/keyboard` but instead it looks like
`../native/keyboa`.

In `rewriteAliasedSourceFileImportPaths` we rewrite aliased paths by
basically:

1. resolving the full path to the aliased module (so e.g. `@utils` is
   resolved to `src/utils/index.ts`)
2. calculating the relative path from the importing module (where there
   is an import like `import { foo } from '@utils';`). This might look
   something like `../../utils/index.ts`.
3. re-writing the resolved relative path to remove the file extension,
   giving us something like `../../utils/index`.

The problem arose in the third step. In order to get complete coverage
for all the possible file extensions we could run into we
programmatically construct a regular expression based on
`ts.Extension`. On `main` at present this looks like this:

```ts
  const extensionRegex = new RegExp(
    Object.values(ts.Extension)
      .map((extension) => `${extension}$`)
      .join('|')
  );
```

The problem is that `Object.values(ts.Extension)` looks like this:

```ts
[
  '.ts',
  '.tsx',
  '.d.ts',
  '.js',
  '.jsx',
  '.json',
  '.tsbuildinfo',
  '.mjs',
  '.mts',
  '.d.mts',
  '.cjs',
  '.cts',
  '.d.cts'
]
```

Thus our regular expression will end up having patterns in it like

```ts
/.d.ts$/
```

This will match the substring `rd.ts` at the end of a string like
`../native/keyboard.ts`, because `.` is the wildcard and will match any
character. This is not what we want!

The solution is to replace `"."` in the extensions with `"\\."` to match
only the string `"."`, which was the original intention.
github-merge-queue bot pushed a commit to ionic-team/stencil that referenced this pull request Jun 22, 2023
This fixes an issue (STENCIL-840) which is mentioned here:
ionic-team/ionic-framework#27417 (comment)

The problem is that for _certain_ aliased paths more of the resolve
filepath is getting cut off than intended. In the example referenced in
the discussion above, for instance, the resolved path should look
something like `../native/keyboard` but instead it looks like
`../native/keyboa`.

In `rewriteAliasedSourceFileImportPaths` we rewrite aliased paths by
basically:

1. resolving the full path to the aliased module (so e.g. `@utils` is
   resolved to `src/utils/index.ts`)
2. calculating the relative path from the importing module (where there
   is an import like `import { foo } from '@utils';`). This might look
   something like `../../utils/index.ts`.
3. re-writing the resolved relative path to remove the file extension,
   giving us something like `../../utils/index`.

The problem arose in the third step. In order to get complete coverage
for all the possible file extensions we could run into we
programmatically construct a regular expression based on
`ts.Extension`. On `main` at present this looks like this:

```ts
  const extensionRegex = new RegExp(
    Object.values(ts.Extension)
      .map((extension) => `${extension}$`)
      .join('|')
  );
```

The problem is that `Object.values(ts.Extension)` looks like this:

```ts
[
  '.ts',
  '.tsx',
  '.d.ts',
  '.js',
  '.jsx',
  '.json',
  '.tsbuildinfo',
  '.mjs',
  '.mts',
  '.d.mts',
  '.cjs',
  '.cts',
  '.d.cts'
]
```

Thus our regular expression will end up having patterns in it like

```ts
/.d.ts$/
```

This will match the substring `rd.ts` at the end of a string like
`../native/keyboard.ts`, because `.` is the wildcard and will match any
character. This is not what we want!

The solution is to replace `"."` in the extensions with `"\\."` to match
only the string `"."`, which was the original intention.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
package: core @ionic/core package
Projects
None yet
Development

Successfully merging this pull request may close these issues.

bug: Tab bar is unhidden too quickly after keyboard dismiss
4 participants