Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/api/input-otp.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,16 @@ import States from '@site/static/usage/v9/input-otp/states/index.mdx';

<States />

## Helper & Error Text {/* #helper--error-text */}

Helper and error text can be displayed under the input boxes with the `helperText` and `errorText` properties. The error text isn't displayed unless the `ion-invalid` and `ion-touched` classes are added to the `ion-input-otp`, so errors aren't shown before the user has a chance to enter a code.

In Angular, this is done automatically through form validation. In JavaScript, React and Vue, the classes need to be added manually based on your own validation.

import HelperError from '@site/static/usage/v9/input-otp/helper-error/index.mdx';

<HelperError />

## Pattern {/* #pattern */}

The `pattern` property enables custom validation using regular expressions. It accepts a [string regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Cheatsheet) or [unicode regular expression](https://www.regular-expressions.info/unicode.html) to validate allowed characters. The `pattern` must match the entire value, not just a subset. Default patterns:
Expand Down Expand Up @@ -151,6 +161,20 @@ import CSSProps from '@site/static/usage/v9/input-otp/theming/css-properties/ind

## Accessibility {/* #accessibility */}

### Labels {/* #labels */}

Each input box is announced with its position, such as "Input 1 of 4". When `aria-label` is set on `ion-input-otp`, it's added to the start of each name, such as "PIN, 1 of 4". An element referenced with `aria-labelledby` works the same way.

The `inputLabelFormatter` property can be used to translate the names. It receives the 0-based `index` of the box and the total `length`, and its return value is used as the full name of the box.

```javascript
inputOtp.inputLabelFormatter = (index, length) => `Ziffer ${index + 1} von ${length}`;
```

### Focus Indicator {/* #focus-indicator */}

The focused input box is indicated by its border color. Set `--highlight-color-focused` to change this color, or `--highlight-color-valid` and `--highlight-color-invalid` to change it when the input has a validation state. If you change these colors, adjust `--border-color`, which styles the unfocused boxes, so the focused box still stands out.

### Keyboard Interactions {/* #keyboard-interactions */}

The keyboard navigation for Input OTP follows the [ARIA Authoring Practices Guide](https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/)'s recommendations for composite widgets. It is treated as a composite widget because it contains multiple focusable elements (input boxes) that function as a single control.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
```html
<ion-input-otp
type="text"
inputmode="numeric"
helperText="Enter the code sent to your email"
errorText="Enter all 4 digits"
ngModel
required
minlength="4"
></ion-input-otp>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
```ts
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { IonInputOtp } from '@ionic/angular';

@Component({
selector: 'app-example',
templateUrl: 'example.component.html',
styleUrls: ['example.component.css'],
imports: [IonInputOtp, FormsModule],
})
export class ExampleComponent {}
```
45 changes: 45 additions & 0 deletions static/usage/v9/input-otp/helper-error/demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Input OTP</title>
<link rel="stylesheet" href="../../common.css" />
<script src="../../common.js"></script>
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core@9/dist/ionic/ionic.esm.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core@9/css/ionic.bundle.css" />
</head>

<body>
<ion-app>
<ion-content>
<div class="container">
<ion-input-otp
helper-text="Enter the code sent to your email"
error-text="Enter all 4 digits"
></ion-input-otp>
</div>
</ion-content>
</ion-app>

<script>
const inputOtp = document.querySelector('ion-input-otp');

inputOtp.addEventListener('ionInput', (event) => validate(event));
inputOtp.addEventListener('ionBlur', () => markTouched());

const validate = (event) => {
const value = event.detail.value ?? '';

inputOtp.classList.remove('ion-valid');
inputOtp.classList.remove('ion-invalid');

value.length === 4 ? inputOtp.classList.add('ion-valid') : inputOtp.classList.add('ion-invalid');
};

const markTouched = () => {
inputOtp.classList.add('ion-touched');
};
</script>
</body>
</html>
25 changes: 25 additions & 0 deletions static/usage/v9/input-otp/helper-error/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Playground from '@site/src/components/global/Playground';

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

import angular_example_component_html from './angular/example_component_html.md';
import angular_example_component_ts from './angular/example_component_ts.md';

<Playground
version="9"
code={{
javascript,
react,
vue,
angular: {
files: {
'src/app/example.component.html': angular_example_component_html,
'src/app/example.component.ts': angular_example_component_ts,
},
},
}}
src="usage/v9/input-otp/helper-error/demo.html"
size="200px"
/>
23 changes: 23 additions & 0 deletions static/usage/v9/input-otp/helper-error/javascript.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
```html
<ion-input-otp helper-text="Enter the code sent to your email" error-text="Enter all 4 digits"></ion-input-otp>

<script>
const inputOtp = document.querySelector('ion-input-otp');

inputOtp.addEventListener('ionInput', (event) => validate(event));
inputOtp.addEventListener('ionBlur', () => markTouched());

const validate = (event) => {
const value = event.detail.value ?? '';

inputOtp.classList.remove('ion-valid');
inputOtp.classList.remove('ion-invalid');

value.length === 4 ? inputOtp.classList.add('ion-valid') : inputOtp.classList.add('ion-invalid');
};

const markTouched = () => {
inputOtp.classList.add('ion-touched');
};
</script>
```
28 changes: 28 additions & 0 deletions static/usage/v9/input-otp/helper-error/react.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
```tsx
import React, { useState } from 'react';
import { IonInputOtp } from '@ionic/react';

function Example() {
const [isTouched, setIsTouched] = useState(false);
const [isValid, setIsValid] = useState<boolean>();

const validate = (value: string) => {
setIsValid(value.length === 4);
};

const markTouched = () => {
setIsTouched(true);
};

return (
<IonInputOtp
className={`${isValid && 'ion-valid'} ${isValid === false && 'ion-invalid'} ${isTouched && 'ion-touched'}`}
helperText="Enter the code sent to your email"
errorText="Enter all 4 digits"
onIonInput={(event) => validate(event.detail.value ?? '')}
onIonBlur={() => markTouched()}
></IonInputOtp>
);
}
export default Example;
```
33 changes: 33 additions & 0 deletions static/usage/v9/input-otp/helper-error/vue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
```html

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
```html
```vue

<template>
<ion-input-otp
ref="inputOtp"
helper-text="Enter the code sent to your email"
error-text="Enter all 4 digits"
@ionInput="validate"
@ionBlur="markTouched"
></ion-input-otp>
</template>

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

const inputOtp = ref();

const validate = (event) => {
const value = event.detail.value ?? '';

inputOtp.value.$el.classList.remove('ion-valid');
inputOtp.value.$el.classList.remove('ion-invalid');

value.length === 4
? inputOtp.value.$el.classList.add('ion-valid')
: inputOtp.value.$el.classList.add('ion-invalid');
};

const markTouched = () => {
inputOtp.value.$el.classList.add('ion-touched');
};
</script>
```
Loading