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

docs(autofocus): playground examples for setFocus #3258

Merged
merged 11 commits into from
Nov 28, 2023
238 changes: 238 additions & 0 deletions docs/developing/managing-focus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
---
title: Managing Focus
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

<head>
<title>Managing Focus</title>
<meta
name="description"
content="Learn how to manage focus in Ionic applications using the setFocus API instead of the autofocus attribute."
/>
</head>

Ionic provides a `setFocus` API on components such as [Input](../api/input), [Searchbar](../api/searchbar), and [Textarea](../api/textarea) that allows developers to manually set focus to an element. This API should be used in place of the `autofocus` attribute and called within:

- The `ionViewDidEnter` lifecycle event for routing applications when a page is entered.
- The `didPresent` lifecycle event for overlays when an overlay is presented.
- The `appload` event for vanilla JavaScript applications when the application loads.
- The result of a user gesture or interaction.

## Why not autofocus?

The `autofocus` attribute is a standard HTML attribute that allows developers to set focus to an element when a page loads. This attribute is commonly used to set focus to the first input element on a page. However, the `autofocus` attribute can cause issues in routing applications when navigating between pages. This is because the `autofocus` attribute will set focus to the element when the page loads, but will not set focus to the element when the page is revisited. Learn more about the `autofocus` attribute in the [MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/autofocus).

## Platform Restrictions

There are platform restrictions you should be aware of when using the `setFocus` API, including:

1. Android requires user interaction before setting focus to an element. This can be as simple as a user tapping on the screen.
2. Interactive elements can only focused a result of a user gesture on Mobile Safari (iOS), such as calling `setFocus` as the result of a button click.

## Basic Usage

The example below demonstrates how to use the `setFocus` API to request focus on an input when the user clicks a button.

import Basic from '@site/static/usage/v7/input/set-focus/index.md';

<Basic />

## Routing

Developers can use the `ionViewDidEnter` lifecycle event to set focus to an element when a page is entered.

````mdx-code-block
<Tabs
groupId="framework"
defaultValue="angular"
values={[
{ value: 'angular', label: 'Angular' },
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue' },
]
}>

<TabItem value="angular">

```ts
/* example.component.ts */
import { Component, ViewChild } from '@angular/core';
import { IonInput } from '@ionic/angular';

@Component({
selector: 'app-example',
templateUrl: './example.component.html',
})
export class ExampleComponent {
@ViewChild('input') input!: IonInput;

ionViewDidEnter() {
this.input.setFocus();
}
}
```
</TabItem>
<TabItem value="react">

```tsx
import React, { useRef } from 'react';
import { IonInput, IonPage, useIonViewDidEnter } from '@ionic/react';

const Home = () => {
const input = useRef<HTMLIonInputElement>(null);

useIonViewDidEnter(() => {
input.current?.setFocus();
});

return (
<IonPage>
<IonInput ref={input} label="setFocus" labelPlacement="floating"></IonInput>
</IonPage>
);
};

export default Home;
```

</TabItem>
<TabItem value="vue">

```html
<template>
<ion-page>
<ion-input ref="input" label="setFocus" label-placement="floating"></ion-input>
</ion-page>
</template>

<script setup lang="ts">
import { IonInput, IonPage, onIonViewDidEnter } from '@ionic/vue';
import { ref } from 'vue';

const input = ref();
onIonViewDidEnter(() => {
requestAnimationFrame(() => {
// requestAnimationFrame is currently required due to:
// https://github.com/ionic-team/ionic-framework/issues/24434
input.value.$el.setFocus();
});
});
</script>
```

</TabItem>
</Tabs>
````

## Overlays

Developers can use the `didPresent` lifecycle event to set focus to an element when an overlay is presented.

````mdx-code-block
<Tabs
groupId="framework"
defaultValue="javascript"
values={[
{ value: 'javascript', label: 'Javascript' },
{ value: 'angular', label: 'Angular' },
{ value: 'react', label: 'React' },
{ value: 'vue', label: 'Vue' },
]
}>

<TabItem value="javascript">

```html
<ion-modal>
<ion-input></ion-input>
</ion-modal>

<script>
const modal = document.querySelector('ion-modal');
modal.addEventListener('didPresent', () => {
const input = modal.querySelector('ion-input');
input.setFocus();
});
</script>
```

</TabItem>

<TabItem value="angular">

```ts
/* example.component.ts */
import { Component, ViewChild } from '@angular/core';
import { IonInput } from '@ionic/angular';

@Component({
selector: 'app-example',
templateUrl: './example.component.html',
})
export class ExampleComponent {
@ViewChild('input') input!: IonInput;

onDidPresent() {
this.input.setFocus();
}
}
```

```html
<!-- example.component.html -->
<ion-modal (didPresent)="onDidPresent()">
<ion-input #input></ion-input>
</ion-modal>
```

</TabItem>
<TabItem value="react">

```tsx
import React, { useRef } from 'react';
import { IonInput, IonModal } from '@ionic/react';

const Home = () => {
const input = useRef<HTMLIonInputElement>(null);

const onDidPresent = () => {
input.current?.setFocus();
};

return (
<IonModal onDidPresent={onDidPresent}>
<IonInput ref={input}></IonInput>
</IonModal>
);
};

export default Home;
```

</TabItem>
<TabItem value="vue">

```html
<template>
<ion-modal @didPresent="onDidPresent">
<ion-input ref="input"></ion-input>
</ion-modal>
</template>

<script setup lang="ts">
import { IonInput, IonModal } from '@ionic/vue';
import { ref } from 'vue';

const input = ref();

function onDidPresent() {
input.value.$el.setFocus();
}
</script>
```

</TabItem>
</Tabs>
````
1 change: 1 addition & 0 deletions sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ module.exports = {
'developing/hardware-back-button',
'developing/keyboard',
'developing/config',
'developing/managing-focus',
],
},
{
Expand Down
10 changes: 10 additions & 0 deletions static/usage/v7/input/set-focus/angular.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
```html
<ion-list>
<ion-item>
<ion-button (click)="input.setFocus()">Click to set focus</ion-button>
</ion-item>
<ion-item>
<ion-input #input label="Email" labelPlacement="floating"></ion-input>
</ion-item>
</ion-list>
```
33 changes: 33 additions & 0 deletions static/usage/v7/input/set-focus/demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>setFocus</title>
<link rel="stylesheet" href="../../../common.css" />
<script src="../../../common.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core@7/dist/ionic/ionic.esm.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core@7/css/ionic.bundle.css" />
</head>

<body>
<ion-app>
<ion-content>
<ion-list>
<ion-item>
<ion-button onclick="setFocus()">Click to set focus</ion-button>
</ion-item>
<ion-item>
<ion-input label="Email" label-placement="floating"></ion-input>
</ion-item>
</ion-list>
</ion-content>
</ion-app>
<script>
function setFocus() {
const input = document.querySelector('ion-input');
input.setFocus();
}
</script>
</body>
</html>
17 changes: 17 additions & 0 deletions static/usage/v7/input/set-focus/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Playground from '@site/src/components/global/Playground';

import javascript from './javascript.md';
import angular from './angular.md';
import vue from './vue.md';
import react from './react.md';

<Playground
version="7"
code={{
javascript,
vue,
angular,
react,
}}
src="usage/v7/input/set-focus/demo.html"
/>
17 changes: 17 additions & 0 deletions static/usage/v7/input/set-focus/javascript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
```html
<ion-list>
<ion-item>
<ion-button onclick="setFocus()">Click to set focus</ion-button>
</ion-item>
<ion-item>
<ion-input label="Email" label-placement="floating"></ion-input>
</ion-item>
</ion-list>

<script>
function setFocus() {
const input = document.querySelector('ion-input');
input.setFocus();
}
</script>
```
21 changes: 21 additions & 0 deletions static/usage/v7/input/set-focus/react.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
```tsx
import React, { useRef } from 'react';
import { IonInput, IonItem, IonList, IonButton } from '@ionic/react';

const Home = () => {
const input = useRef<HTMLIonInputElement>(null);

return (
<IonList>
<IonItem>
<IonButton onClick={() => input.current?.setFocus()}>Click to set focus</IonButton>
</IonItem>
<IonItem>
<IonInput ref={input} label="Email" labelPlacement="floating"></IonInput>
</IonItem>
</IonList>
);
};

export default Home;
```
23 changes: 23 additions & 0 deletions static/usage/v7/input/set-focus/vue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
```html
<template>
<ion-list>
<ion-item>
<ion-button @click="setFocus">Click to set focus</ion-button>
</ion-item>
<ion-item>
<ion-input ref="input" label="Email" label-placement="floating"></ion-input>
</ion-item>
</ion-list>
</template>

<script setup lang="ts">
import { IonInput, IonItem, IonList, IonButton } from '@ionic/vue';
import { ref } from 'vue';

const input = ref();

function setFocus() {
input.value.$el.setFocus();
}
</script>
```