Skip to content

Commit 2d0441c

Browse files
committed
fix(input-otp): update implementation to match input's more closely
1 parent d5c7c22 commit 2d0441c

File tree

9 files changed

+111
-90
lines changed

9 files changed

+111
-90
lines changed

core/api.txt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -784,16 +784,16 @@ ion-input-otp,scoped
784784
ion-input-otp,prop,allowedKeys,string | undefined,undefined,false,false
785785
ion-input-otp,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record<never, never> | undefined,undefined,false,true
786786
ion-input-otp,prop,disabled,boolean,false,false,true
787-
ion-input-otp,prop,fill,"outline" | "solid",'outline',false,false
787+
ion-input-otp,prop,fill,"outline" | "solid" | undefined,'outline',false,false
788788
ion-input-otp,prop,inputmode,"decimal" | "email" | "none" | "numeric" | "search" | "tel" | "text" | "url" | undefined,undefined,false,false
789789
ion-input-otp,prop,length,number,4,false,false
790790
ion-input-otp,prop,separators,number[] | string | undefined,undefined,false,false
791791
ion-input-otp,prop,shape,"rectangular" | "round" | "soft",'round',false,false
792792
ion-input-otp,prop,size,"large" | "medium" | "small",'medium',false,false
793793
ion-input-otp,prop,type,"number" | "text",'number',false,false
794-
ion-input-otp,prop,value,string | undefined,'',false,false
795-
ion-input-otp,event,ionChange,InputOTPChangeEventDetail,true
796-
ion-input-otp,event,ionComplete,InputOTPCompleteEventDetail,true
794+
ion-input-otp,prop,value,null | number | string | undefined,'',false,false
795+
ion-input-otp,event,ionChange,InputOtpChangeEventDetail,true
796+
ion-input-otp,event,ionComplete,InputOtpCompleteEventDetail,true
797797
ion-input-otp,css-prop,--background,ios
798798
ion-input-otp,css-prop,--background,md
799799
ion-input-otp,css-prop,--background-focused,ios
@@ -810,8 +810,8 @@ ion-input-otp,css-prop,--color,ios
810810
ion-input-otp,css-prop,--color,md
811811
ion-input-otp,css-prop,--height,ios
812812
ion-input-otp,css-prop,--height,md
813-
ion-input-otp,css-prop,--highlight-color,ios
814-
ion-input-otp,css-prop,--highlight-color,md
813+
ion-input-otp,css-prop,--highlight-color-invalid,ios
814+
ion-input-otp,css-prop,--highlight-color-invalid,md
815815
ion-input-otp,css-prop,--margin-bottom,ios
816816
ion-input-otp,css-prop,--margin-bottom,md
817817
ion-input-otp,css-prop,--margin-end,ios

core/src/components.d.ts

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { ScrollBaseDetail, ScrollDetail } from "./components/content/content-int
1818
import { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
1919
import { SpinnerTypes } from "./components/spinner/spinner-configs";
2020
import { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
21-
import { InputOTPChangeEventDetail, InputOTPCompleteEventDetail } from "./components/input-otp/input-otp";
21+
import { InputOtpChangeEventDetail, InputOtpCompleteEventDetail } from "./components/input-otp/input-otp-interface";
2222
import { MenuChangeEventDetail, MenuCloseEventDetail, MenuType, Side } from "./components/menu/menu-interface";
2323
import { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface";
2424
import { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface";
@@ -56,7 +56,7 @@ export { ScrollBaseDetail, ScrollDetail } from "./components/content/content-int
5656
export { DatetimeChangeEventDetail, DatetimeHighlight, DatetimeHighlightCallback, DatetimeHourCycle, DatetimePresentation, FormatOptions, TitleSelectedDatesFormatter } from "./components/datetime/datetime-interface";
5757
export { SpinnerTypes } from "./components/spinner/spinner-configs";
5858
export { InputChangeEventDetail, InputInputEventDetail } from "./components/input/input-interface";
59-
export { InputOTPChangeEventDetail, InputOTPCompleteEventDetail } from "./components/input-otp/input-otp";
59+
export { InputOtpChangeEventDetail, InputOtpCompleteEventDetail } from "./components/input-otp/input-otp-interface";
6060
export { MenuChangeEventDetail, MenuCloseEventDetail, MenuType, Side } from "./components/menu/menu-interface";
6161
export { ModalBreakpointChangeEventDetail, ModalHandleBehavior } from "./components/modal/modal-interface";
6262
export { NavComponent, NavComponentWithProps, NavOptions, RouterOutletOptions, SwipeGestureHandler, TransitionDoneFn, TransitionInstruction } from "./components/nav/nav-interface";
@@ -1327,45 +1327,45 @@ export namespace Components {
13271327
*/
13281328
"allowedKeys"?: string;
13291329
/**
1330-
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
1330+
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
13311331
*/
13321332
"color"?: Color;
13331333
/**
1334-
* Whether the input is disabled
1334+
* If `true`, the user cannot interact with the input.
13351335
*/
13361336
"disabled": boolean;
13371337
/**
1338-
* The fill style of the input boxes
1338+
* The fill for the input boxes. If `"solid"` the input boxes will have a background. If `"outline"` the input boxes will be transparent with a border.
13391339
*/
1340-
"fill": 'solid' | 'outline';
1340+
"fill"?: 'outline' | 'solid';
13411341
/**
13421342
* A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`. For numbers (type="number"): "numeric" For text (type="text"): "text"
13431343
*/
13441344
"inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
13451345
/**
1346-
* The number of input boxes to display
1346+
* The number of input boxes to display.
13471347
*/
13481348
"length": number;
13491349
/**
1350-
* Where separators should be shown between input boxes. Can be a comma-separated string or an array of numbers. For example: "3" would show a separator after the 3rd input box. [1,4] would show a separator after the 1st and 4th input boxes.
1350+
* Where separators should be shown between input boxes. Can be a comma-separated string or an array of numbers. For example: `"3"` will show a separator after the 3rd input box. `[1,4]` will show a separator after the 1st and 4th input boxes. `"all"` will show a separator between every input box.
13511351
*/
13521352
"separators"?: 'all' | string | number[];
13531353
/**
1354-
* The shape of the input boxes
1354+
* The shape of the input boxes. If "round" they will have an increased border radius. If "rectangular" they will have no border radius. If "soft" they will have a soft border radius.
13551355
*/
13561356
"shape": 'round' | 'rectangular' | 'soft';
13571357
/**
1358-
* The size of the input boxes
1358+
* The size of the input boxes.
13591359
*/
13601360
"size": 'small' | 'medium' | 'large';
13611361
/**
1362-
* The type of input allowed in the boxes
1362+
* The type of input allowed in the input boxes.
13631363
*/
13641364
"type": 'text' | 'number';
13651365
/**
1366-
* The value of the OTP input
1366+
* The value of the OTP input.
13671367
*/
1368-
"value"?: string;
1368+
"value"?: string | number | null;
13691369
}
13701370
interface IonInputPasswordToggle {
13711371
/**
@@ -3986,8 +3986,8 @@ declare global {
39863986
new (): HTMLIonInputElement;
39873987
};
39883988
interface HTMLIonInputOtpElementEventMap {
3989-
"ionChange": InputOTPChangeEventDetail;
3990-
"ionComplete": InputOTPCompleteEventDetail;
3989+
"ionChange": InputOtpChangeEventDetail;
3990+
"ionComplete": InputOtpCompleteEventDetail;
39913991
}
39923992
interface HTMLIonInputOtpElement extends Components.IonInputOtp, HTMLStencilElement {
39933993
addEventListener<K extends keyof HTMLIonInputOtpElementEventMap>(type: K, listener: (this: HTMLIonInputOtpElement, ev: IonInputOtpCustomEvent<HTMLIonInputOtpElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
@@ -6255,53 +6255,53 @@ declare namespace LocalJSX {
62556255
*/
62566256
"allowedKeys"?: string;
62576257
/**
6258-
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
6258+
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
62596259
*/
62606260
"color"?: Color;
62616261
/**
6262-
* Whether the input is disabled
6262+
* If `true`, the user cannot interact with the input.
62636263
*/
62646264
"disabled"?: boolean;
62656265
/**
6266-
* The fill style of the input boxes
6266+
* The fill for the input boxes. If `"solid"` the input boxes will have a background. If `"outline"` the input boxes will be transparent with a border.
62676267
*/
6268-
"fill"?: 'solid' | 'outline';
6268+
"fill"?: 'outline' | 'solid';
62696269
/**
62706270
* A hint to the browser for which keyboard to display. Possible values: `"none"`, `"text"`, `"tel"`, `"url"`, `"email"`, `"numeric"`, `"decimal"`, and `"search"`. For numbers (type="number"): "numeric" For text (type="text"): "text"
62716271
*/
62726272
"inputmode"?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
62736273
/**
6274-
* The number of input boxes to display
6274+
* The number of input boxes to display.
62756275
*/
62766276
"length"?: number;
62776277
/**
62786278
* Emitted when the value changes
62796279
*/
6280-
"onIonChange"?: (event: IonInputOtpCustomEvent<InputOTPChangeEventDetail>) => void;
6280+
"onIonChange"?: (event: IonInputOtpCustomEvent<InputOtpChangeEventDetail>) => void;
62816281
/**
62826282
* Emitted when the input is complete (all boxes filled)
62836283
*/
6284-
"onIonComplete"?: (event: IonInputOtpCustomEvent<InputOTPCompleteEventDetail>) => void;
6284+
"onIonComplete"?: (event: IonInputOtpCustomEvent<InputOtpCompleteEventDetail>) => void;
62856285
/**
6286-
* Where separators should be shown between input boxes. Can be a comma-separated string or an array of numbers. For example: "3" would show a separator after the 3rd input box. [1,4] would show a separator after the 1st and 4th input boxes.
6286+
* Where separators should be shown between input boxes. Can be a comma-separated string or an array of numbers. For example: `"3"` will show a separator after the 3rd input box. `[1,4]` will show a separator after the 1st and 4th input boxes. `"all"` will show a separator between every input box.
62876287
*/
62886288
"separators"?: 'all' | string | number[];
62896289
/**
6290-
* The shape of the input boxes
6290+
* The shape of the input boxes. If "round" they will have an increased border radius. If "rectangular" they will have no border radius. If "soft" they will have a soft border radius.
62916291
*/
62926292
"shape"?: 'round' | 'rectangular' | 'soft';
62936293
/**
6294-
* The size of the input boxes
6294+
* The size of the input boxes.
62956295
*/
62966296
"size"?: 'small' | 'medium' | 'large';
62976297
/**
6298-
* The type of input allowed in the boxes
6298+
* The type of input allowed in the input boxes.
62996299
*/
63006300
"type"?: 'text' | 'number';
63016301
/**
6302-
* The value of the OTP input
6302+
* The value of the OTP input.
63036303
*/
6304-
"value"?: string;
6304+
"value"?: string | number | null;
63056305
}
63066306
interface IonInputPasswordToggle {
63076307
/**
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Values are converted to strings when emitted which is
3+
* why we do not have a `number` type here even though the
4+
* `value` prop accepts a `number` type.
5+
*/
6+
export interface InputOtpChangeEventDetail {
7+
complete: boolean;
8+
value: string | null;
9+
event?: Event;
10+
}
11+
12+
export interface InputOtpCompleteEventDetail {
13+
value?: string | null;
14+
event?: Event;
15+
}
16+
17+
export interface InputOtpCustomEvent<T = InputOtpChangeEventDetail> extends CustomEvent {
18+
detail: T;
19+
target: HTMLIonInputOtpElement;
20+
}

core/src/components/input-otp/input-otp.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@
8787
height: var(--height);
8888

8989
border-width: var(--border-width);
90-
border-color: var(--border-color);
9190
border-style: solid;
91+
border-color: var(--border-color);
9292

9393
background: var(--background, var(--ion-background-color));
9494
color: var(--color, var(--ion-text-color));

core/src/components/input-otp/input-otp.tsx

Lines changed: 38 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,7 @@ import { createColorClasses } from '@utils/theme';
55
import { getIonMode } from '../../global/ionic-global';
66
import type { Color } from '../../interface';
77

8-
export interface InputOTPChangeEventDetail {
9-
value: string;
10-
complete: boolean;
11-
}
12-
13-
export interface InputOTPCompleteEventDetail {
14-
value: string;
15-
}
16-
17-
export interface HTMLIonInputOTPElement extends HTMLElement {
18-
value?: string;
19-
}
8+
import type { InputOtpChangeEventDetail, InputOtpCompleteEventDetail } from './input-otp-interface';
209

2110
@Component({
2211
tag: 'ion-input-otp',
@@ -30,34 +19,28 @@ export class InputOTP implements ComponentInterface {
3019
private inputRefs: HTMLInputElement[] = [];
3120
private inputId = `ion-input-otp-${inputIds++}`;
3221

33-
@Element() el!: HTMLIonInputOTPElement;
22+
@Element() el!: HTMLIonInputOtpElement;
3423

3524
@State() private inputValues: string[] = [];
3625
@State() hasFocus = false;
3726

3827
/**
3928
* The color to use from your application's color palette.
4029
* Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`.
30+
* For more information on colors, see [theming](/docs/theming/basics).
4131
*/
4232
@Prop({ reflect: true }) color?: Color;
4333

4434
/**
45-
* The number of input boxes to display
46-
*/
47-
@Prop() length = 4;
48-
49-
/**
50-
* The type of input allowed in the boxes
35+
* If `true`, the user cannot interact with the input.
5136
*/
52-
@Prop() type: 'text' | 'number' = 'number';
37+
@Prop({ reflect: true }) disabled = false;
5338

5439
/**
55-
* A regex pattern string for allowed characters. Defaults based on type.
56-
*
57-
* For numbers (type="number"): "[0-9]"
58-
* For text (type="text"): "[a-zA-Z0-9]"
40+
* The fill for the input boxes. If `"solid"` the input boxes will have a background. If
41+
* `"outline"` the input boxes will be transparent with a border.
5942
*/
60-
@Prop() allowedKeys?: string;
43+
@Prop() fill?: 'outline' | 'solid' = 'outline';
6144

6245
/**
6346
* A hint to the browser for which keyboard to display.
@@ -70,48 +53,61 @@ export class InputOTP implements ComponentInterface {
7053
@Prop() inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
7154

7255
/**
73-
* The size of the input boxes
56+
* The number of input boxes to display.
57+
*/
58+
@Prop() length = 4;
59+
60+
/**
61+
* A regex pattern string for allowed characters. Defaults based on type.
62+
*
63+
* For numbers (type="number"): "[0-9]"
64+
* For text (type="text"): "[a-zA-Z0-9]"
65+
*/
66+
@Prop() allowedKeys?: string;
67+
68+
/**
69+
* The size of the input boxes.
7470
*/
7571
@Prop() size: 'small' | 'medium' | 'large' = 'medium';
7672

7773
/**
7874
* Where separators should be shown between input boxes.
7975
* Can be a comma-separated string or an array of numbers.
76+
*
8077
* For example:
81-
* "3" would show a separator after the 3rd input box.
82-
* [1,4] would show a separator after the 1st and 4th input boxes.
78+
* `"3"` will show a separator after the 3rd input box.
79+
* `[1,4]` will show a separator after the 1st and 4th input boxes.
80+
* `"all"` will show a separator between every input box.
8381
*/
8482
@Prop() separators?: 'all' | string | number[];
8583

8684
/**
87-
* The fill style of the input boxes
88-
*/
89-
@Prop() fill: 'solid' | 'outline' = 'outline';
90-
91-
/**
92-
* The shape of the input boxes
85+
* The shape of the input boxes.
86+
* If "round" they will have an increased border radius.
87+
* If "rectangular" they will have no border radius.
88+
* If "soft" they will have a soft border radius.
9389
*/
9490
@Prop() shape: 'round' | 'rectangular' | 'soft' = 'round';
9591

9692
/**
97-
* Whether the input is disabled
93+
* The type of input allowed in the input boxes.
9894
*/
99-
@Prop({ reflect: true }) disabled = false;
95+
@Prop() type: 'text' | 'number' = 'number';
10096

10197
/**
102-
* The value of the OTP input
98+
* The value of the OTP input.
10399
*/
104-
@Prop({ mutable: true }) value?: string = '';
100+
@Prop({ mutable: true }) value?: string | number | null = '';
105101

106102
/**
107103
* Emitted when the value changes
108104
*/
109-
@Event() ionChange!: EventEmitter<InputOTPChangeEventDetail>;
105+
@Event() ionChange!: EventEmitter<InputOtpChangeEventDetail>;
110106

111107
/**
112108
* Emitted when the input is complete (all boxes filled)
113109
*/
114-
@Event() ionComplete!: EventEmitter<InputOTPCompleteEventDetail>;
110+
@Event() ionComplete!: EventEmitter<InputOtpCompleteEventDetail>;
115111

116112
@Watch('value')
117113
valueChanged() {
@@ -123,8 +119,8 @@ export class InputOTP implements ComponentInterface {
123119
}
124120

125121
private initializeValues() {
126-
if (this.value && this.value.length > 0) {
127-
const chars = this.value.split('').slice(0, this.length);
122+
if (this.value != null && String(this.value).length > 0) {
123+
const chars = String(this.value).split('').slice(0, this.length);
128124
chars.forEach((char, index) => {
129125
if (this.validKeys.test(char.toLowerCase())) {
130126
this.inputValues[index] = char;

core/src/components/input-otp/test/basic/index.html

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
<h2>Default</h2>
4242
<ion-input-otp> Didn't get a code? <a href="#">Resend the code</a> </ion-input-otp>
4343
<ion-input-otp length="2"> Didn't get a code? <a href="#">Resend the code</a> </ion-input-otp>
44-
<ion-input-otp value="123" length="6"> Didn't get a code? <a href="#">Resend the code</a> </ion-input-otp>
44+
<ion-input-otp id="numberValue" length="6"> Didn't get a code? <a href="#">Resend the code</a> </ion-input-otp>
4545
<ion-input-otp value="5893" length="8"> Didn't get a code? <a href="#">Resend the code</a> </ion-input-otp>
4646
</div>
4747

@@ -65,6 +65,11 @@ <h2>Types</h2>
6565
</div>
6666
</div>
6767
</ion-content>
68+
69+
<script>
70+
const numberValue = document.getElementById('numberValue');
71+
numberValue.value = 123;
72+
</script>
6873
</ion-app>
6974
</body>
7075
</html>

0 commit comments

Comments
 (0)