Skip to content

Commit e8be93a

Browse files
committed
fix(angular): add input-otp to Angular's value accessors
1 parent e02aa1a commit e8be93a

File tree

5 files changed

+142
-5
lines changed

5 files changed

+142
-5
lines changed

packages/angular/src/directives/control-value-accessors/numeric-value-accessor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms';
33
import { ValueAccessor } from '@ionic/angular/common';
44

55
@Directive({
6-
selector: 'ion-input[type=number],ion-range',
6+
selector: 'ion-input[type=number],ion-input-otp:not([type=text]),ion-range',
77
providers: [
88
{
99
provide: NG_VALUE_ACCESSOR,
@@ -18,12 +18,12 @@ export class NumericValueAccessorDirective extends ValueAccessor {
1818
}
1919

2020
@HostListener('ionInput', ['$event.target'])
21-
handleInputEvent(el: HTMLIonInputElement | HTMLIonRangeElement): void {
21+
handleInputEvent(el: HTMLIonInputElement | HTMLIonInputOtpElement | HTMLIonRangeElement): void {
2222
this.handleValueChange(el, el.value);
2323
}
2424

2525
registerOnChange(fn: (_: number | null) => void): void {
26-
if (this.el.nativeElement.tagName === 'ION-INPUT') {
26+
if (this.el.nativeElement.tagName === 'ION-INPUT' || this.el.nativeElement.tagName === 'ION-INPUT-OTP') {
2727
super.registerOnChange((value: string) => {
2828
fn(value === '' ? null : parseFloat(value));
2929
});

packages/angular/src/directives/control-value-accessors/text-value-accessor.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { NG_VALUE_ACCESSOR } from '@angular/forms';
33
import { ValueAccessor } from '@ionic/angular/common';
44

55
@Directive({
6-
selector: 'ion-input:not([type=number]),ion-textarea,ion-searchbar',
6+
selector: 'ion-input:not([type=number]),ion-input-otp[type=text],ion-textarea,ion-searchbar',
77
providers: [
88
{
99
provide: NG_VALUE_ACCESSOR,
@@ -18,7 +18,9 @@ export class TextValueAccessorDirective extends ValueAccessor {
1818
}
1919

2020
@HostListener('ionInput', ['$event.target'])
21-
_handleInputEvent(el: HTMLIonInputElement | HTMLIonTextareaElement | HTMLIonSearchbarElement): void {
21+
_handleInputEvent(
22+
el: HTMLIonInputElement | HTMLIonInputOtpElement | HTMLIonTextareaElement | HTMLIonSearchbarElement
23+
): void {
2224
this.handleValueChange(el, el.value);
2325
}
2426
}

packages/angular/standalone/src/directives/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './checkbox';
22
export * from './datetime';
33
export * from './icon';
44
export * from './input';
5+
export * from './input-otp';
56
export * from './radio-group';
67
export * from './range';
78
export * from './searchbar';
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import {
2+
ChangeDetectionStrategy,
3+
ChangeDetectorRef,
4+
Component,
5+
ElementRef,
6+
EventEmitter,
7+
HostListener,
8+
Injector,
9+
NgZone,
10+
forwardRef,
11+
} from '@angular/core';
12+
import { NG_VALUE_ACCESSOR } from '@angular/forms';
13+
import { ValueAccessor } from '@ionic/angular/common';
14+
import type {
15+
InputOtpInputEventDetail as IIonInputOtpInputEventDetail,
16+
InputOtpChangeEventDetail as IIonInputOtpChangeEventDetail,
17+
InputOtpCompleteEventDetail as IIonInputOtpCompleteEventDetail,
18+
Components,
19+
} from '@ionic/core/components';
20+
import { defineCustomElement } from '@ionic/core/components/ion-input-otp.js';
21+
22+
import { ProxyCmp, proxyOutputs } from './angular-component-lib/utils';
23+
24+
const INPUT_OTP_INPUTS = [
25+
'autocapitalize',
26+
'color',
27+
'disabled',
28+
'fill',
29+
'inputmode',
30+
'length',
31+
'pattern',
32+
'readonly',
33+
'separators',
34+
'shape',
35+
'size',
36+
'type',
37+
'value',
38+
];
39+
40+
/**
41+
* Pulling the provider into an object and using PURE works
42+
* around an ng-packagr issue that causes
43+
* components with multiple decorators and
44+
* a provider to be re-assigned. This re-assignment
45+
* is not supported by Webpack and causes treeshaking
46+
* to not work on these kinds of components.
47+
*/
48+
const accessorProvider = {
49+
provide: NG_VALUE_ACCESSOR,
50+
useExisting: /*@__PURE__*/ forwardRef(() => IonInputOtp),
51+
multi: true,
52+
};
53+
54+
@ProxyCmp({
55+
defineCustomElementFn: defineCustomElement,
56+
inputs: INPUT_OTP_INPUTS,
57+
methods: ['reset', 'setFocus'],
58+
})
59+
@Component({
60+
selector: 'ion-input-otp',
61+
changeDetection: ChangeDetectionStrategy.OnPush,
62+
template: '<ng-content></ng-content>',
63+
// eslint-disable-next-line @angular-eslint/no-inputs-metadata-property
64+
inputs: INPUT_OTP_INPUTS,
65+
providers: [accessorProvider],
66+
standalone: true,
67+
})
68+
export class IonInputOtp extends ValueAccessor {
69+
protected el: HTMLElement;
70+
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone, injector: Injector) {
71+
super(injector, r);
72+
c.detach();
73+
this.el = r.nativeElement;
74+
proxyOutputs(this, this.el, ['ionInput', 'ionChange', 'ionComplete', 'ionBlur', 'ionFocus']);
75+
}
76+
77+
@HostListener('ionInput', ['$event.target'])
78+
handleIonInput(el: HTMLIonInputOtpElement): void {
79+
this.handleValueChange(el, el.value);
80+
}
81+
82+
registerOnChange(fn: (_: any) => void): void {
83+
super.registerOnChange((value: string) => {
84+
if (this.type === 'number') {
85+
/**
86+
* If the input type is `number`, we need to convert the value to a number
87+
* when the value is not empty. If the value is empty, we want to treat
88+
* the value as null.
89+
*/
90+
fn(value === '' ? null : parseFloat(value));
91+
} else {
92+
fn(value);
93+
}
94+
});
95+
}
96+
}
97+
98+
export declare interface IonInputOtp extends Components.IonInputOtp {
99+
/**
100+
* The `ionInput` event is fired each time the user modifies the input's value.
101+
* Unlike the `ionChange` event, the `ionInput` event is fired for each alteration
102+
* to the input's value. This typically happens for each keystroke as the user types.
103+
*
104+
* For elements that accept text input (`type=text`, `type=tel`, etc.), the interface
105+
* is [`InputEvent`](https://developer.mozilla.org/en-US/docs/Web/API/InputEvent); for others,
106+
* the interface is [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event). If
107+
* the input is cleared on edit, the type is `null`.
108+
*/
109+
ionInput: EventEmitter<CustomEvent<IIonInputOtpInputEventDetail>>;
110+
/**
111+
* The `ionChange` event is fired when the user modifies the input's value.
112+
* Unlike the `ionInput` event, the `ionChange` event is only fired when changes
113+
* are committed, not as the user types.
114+
*
115+
* The `ionChange` event fires when the `<ion-input-otp>` component loses
116+
* focus after its value has changed.
117+
*
118+
* This event will not emit when programmatically setting the `value` property.
119+
*/
120+
ionChange: EventEmitter<CustomEvent<IIonInputOtpChangeEventDetail>>;
121+
/**
122+
* Emitted when all input boxes have been filled with valid values.
123+
*/
124+
ionComplete: EventEmitter<CustomEvent<IIonInputOtpCompleteEventDetail>>;
125+
/**
126+
* Emitted when the input group loses focus.
127+
*/
128+
ionBlur: EventEmitter<CustomEvent<FocusEvent>>;
129+
/**
130+
* Emitted when the input group has focus.
131+
*/
132+
ionFocus: EventEmitter<CustomEvent<FocusEvent>>;
133+
}

packages/angular/standalone/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export {
3232
IonCheckbox,
3333
IonDatetime,
3434
IonInput,
35+
IonInputOtp,
3536
IonIcon,
3637
IonRadioGroup,
3738
IonRange,

0 commit comments

Comments
 (0)