ng-primitives 0.87.0 → 0.88.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/ng-primitives-combobox.mjs +6 -0
- package/fesm2022/ng-primitives-combobox.mjs.map +1 -1
- package/fesm2022/ng-primitives-input-otp.mjs +514 -0
- package/fesm2022/ng-primitives-input-otp.mjs.map +1 -0
- package/input-otp/README.md +3 -0
- package/input-otp/index.d.ts +4 -0
- package/input-otp/input-otp/input-otp-state.d.ts +17 -0
- package/input-otp/input-otp/input-otp.d.ts +133 -0
- package/input-otp/input-otp-input/input-otp-input.d.ts +57 -0
- package/input-otp/input-otp-slot/input-otp-slot.d.ts +45 -0
- package/package.json +24 -20
- package/schematics/ng-generate/schema.d.ts +2 -1
- package/schematics/ng-generate/schema.json +2 -1
- package/schematics/ng-generate/templates/input-otp/input-otp.__fileSuffix@dasherize__.ts.template +197 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { BooleanInput } from '@angular/cdk/coercion';
|
|
2
|
+
import type { NgpInputOtpInput } from '../input-otp-input/input-otp-input';
|
|
3
|
+
import type { NgpInputOtpSlot } from '../input-otp-slot/input-otp-slot';
|
|
4
|
+
import * as i0 from "@angular/core";
|
|
5
|
+
export type NgpInputOtpInputMode = 'numeric' | 'text' | 'decimal' | 'tel' | 'search' | 'email' | 'url';
|
|
6
|
+
export declare class NgpInputOtp {
|
|
7
|
+
/**
|
|
8
|
+
* Access the element reference.
|
|
9
|
+
*/
|
|
10
|
+
readonly elementRef: import("@angular/core").ElementRef<HTMLElement>;
|
|
11
|
+
/**
|
|
12
|
+
* The id of the input-otp.
|
|
13
|
+
*/
|
|
14
|
+
readonly id: import("@angular/core").InputSignal<string>;
|
|
15
|
+
/**
|
|
16
|
+
* The current value of the OTP.
|
|
17
|
+
*/
|
|
18
|
+
readonly value: import("@angular/core").InputSignal<string>;
|
|
19
|
+
/**
|
|
20
|
+
* The regex pattern for allowed characters.
|
|
21
|
+
*/
|
|
22
|
+
readonly pattern: import("@angular/core").InputSignal<string>;
|
|
23
|
+
/**
|
|
24
|
+
* The input mode for the hidden input.
|
|
25
|
+
*/
|
|
26
|
+
readonly inputMode: import("@angular/core").InputSignal<NgpInputOtpInputMode>;
|
|
27
|
+
/**
|
|
28
|
+
* Function to transform pasted text.
|
|
29
|
+
*/
|
|
30
|
+
readonly pasteTransformer: import("@angular/core").InputSignal<((text: string) => string) | undefined>;
|
|
31
|
+
/**
|
|
32
|
+
* Whether the input-otp is disabled.
|
|
33
|
+
*/
|
|
34
|
+
readonly disabled: import("@angular/core").InputSignalWithTransform<boolean, BooleanInput>;
|
|
35
|
+
/**
|
|
36
|
+
* The placeholder character to display when a slot is empty.
|
|
37
|
+
*/
|
|
38
|
+
readonly placeholder: import("@angular/core").InputSignal<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Event emitted when the value changes.
|
|
41
|
+
*/
|
|
42
|
+
readonly valueChange: import("@angular/core").OutputEmitterRef<string>;
|
|
43
|
+
/**
|
|
44
|
+
* Event emitted when the OTP is complete (maxLength characters entered).
|
|
45
|
+
*/
|
|
46
|
+
readonly complete: import("@angular/core").OutputEmitterRef<string>;
|
|
47
|
+
/**
|
|
48
|
+
* Store the input element reference.
|
|
49
|
+
* @internal
|
|
50
|
+
*/
|
|
51
|
+
private readonly inputElement;
|
|
52
|
+
/**
|
|
53
|
+
* Store registered slots in order.
|
|
54
|
+
* @internal
|
|
55
|
+
*/
|
|
56
|
+
private readonly slots;
|
|
57
|
+
/**
|
|
58
|
+
* The number of characters in the OTP, derived from registered slots.
|
|
59
|
+
*/
|
|
60
|
+
readonly maxLength: import("@angular/core").Signal<number>;
|
|
61
|
+
/**
|
|
62
|
+
* The focus state of the input.
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
readonly isFocused: import("@angular/core").WritableSignal<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* The selection start position.
|
|
68
|
+
* @internal
|
|
69
|
+
*/
|
|
70
|
+
readonly selectionStart: import("@angular/core").WritableSignal<number>;
|
|
71
|
+
/**
|
|
72
|
+
* The selection end position.
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
75
|
+
readonly selectionEnd: import("@angular/core").WritableSignal<number>;
|
|
76
|
+
/**
|
|
77
|
+
* The state of the input-otp.
|
|
78
|
+
*/
|
|
79
|
+
protected readonly state: import("ng-primitives/state").CreatedState<NgpInputOtp>;
|
|
80
|
+
constructor();
|
|
81
|
+
/**
|
|
82
|
+
* Register an input element with the input-otp.
|
|
83
|
+
* @param input The input element to register.
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
registerInput(input: NgpInputOtpInput): void;
|
|
87
|
+
/**
|
|
88
|
+
* Register a slot with the input-otp.
|
|
89
|
+
* @param slot The slot to register.
|
|
90
|
+
* @internal
|
|
91
|
+
*/
|
|
92
|
+
registerSlot(slot: NgpInputOtpSlot): void;
|
|
93
|
+
/**
|
|
94
|
+
* Unregister a slot from the input-otp.
|
|
95
|
+
* @param slot The slot to unregister.
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
unregisterSlot(slot: NgpInputOtpSlot): void;
|
|
99
|
+
/**
|
|
100
|
+
* Get the index of a registered slot.
|
|
101
|
+
* @param slot The slot to get the index for.
|
|
102
|
+
* @returns The index of the slot, or -1 if not found.
|
|
103
|
+
* @internal
|
|
104
|
+
*/
|
|
105
|
+
getSlotIndex(slot: NgpInputOtpSlot): number;
|
|
106
|
+
/**
|
|
107
|
+
* Update the value and emit change events.
|
|
108
|
+
* @param newValue The new value.
|
|
109
|
+
* @internal
|
|
110
|
+
*/
|
|
111
|
+
updateValue(newValue: string): void;
|
|
112
|
+
/**
|
|
113
|
+
* Update focus state.
|
|
114
|
+
* @param focused Whether the input is focused.
|
|
115
|
+
* @internal
|
|
116
|
+
*/
|
|
117
|
+
updateFocus(focused: boolean): void;
|
|
118
|
+
/**
|
|
119
|
+
* Update selection state.
|
|
120
|
+
* @param start Selection start position.
|
|
121
|
+
* @param end Selection end position.
|
|
122
|
+
* @internal
|
|
123
|
+
*/
|
|
124
|
+
updateSelection(start: number, end: number): void;
|
|
125
|
+
/**
|
|
126
|
+
* Focus the input and set caret to the specified position.
|
|
127
|
+
* @param position The position to set the caret to.
|
|
128
|
+
* @internal
|
|
129
|
+
*/
|
|
130
|
+
focusAtPosition(position: number): void;
|
|
131
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NgpInputOtp, never>;
|
|
132
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgpInputOtp, "[ngpInputOtp]", ["ngpInputOtp"], { "id": { "alias": "id"; "required": false; "isSignal": true; }; "value": { "alias": "ngpInputOtpValue"; "required": false; "isSignal": true; }; "pattern": { "alias": "ngpInputOtpPattern"; "required": false; "isSignal": true; }; "inputMode": { "alias": "ngpInputOtpInputMode"; "required": false; "isSignal": true; }; "pasteTransformer": { "alias": "ngpInputOtpPasteTransformer"; "required": false; "isSignal": true; }; "disabled": { "alias": "ngpInputOtpDisabled"; "required": false; "isSignal": true; }; "placeholder": { "alias": "ngpInputOtpPlaceholder"; "required": false; "isSignal": true; }; }, { "valueChange": "ngpInputOtpValueChange"; "complete": "ngpInputOtpComplete"; }, never, never, true, never>;
|
|
133
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { AfterViewInit } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
import * as i1 from "ng-primitives/a11y";
|
|
4
|
+
export declare class NgpInputOtpInput implements AfterViewInit {
|
|
5
|
+
/**
|
|
6
|
+
* Access the element reference.
|
|
7
|
+
*/
|
|
8
|
+
readonly elementRef: import("@angular/core").ElementRef<HTMLInputElement>;
|
|
9
|
+
/**
|
|
10
|
+
* Access the input-otp state.
|
|
11
|
+
*/
|
|
12
|
+
protected readonly state: import("@angular/core").Signal<import("ng-primitives/state").State<import("ng-primitives/input-otp").NgpInputOtp>>;
|
|
13
|
+
constructor();
|
|
14
|
+
ngAfterViewInit(): void;
|
|
15
|
+
/**
|
|
16
|
+
* Focus the input.
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
focus(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Set selection range.
|
|
22
|
+
* @param start Start position
|
|
23
|
+
* @param end End position
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
setSelectionRange(start: number, end: number): void;
|
|
27
|
+
/**
|
|
28
|
+
* Handle input events (typing).
|
|
29
|
+
*/
|
|
30
|
+
protected onInput(event: Event): void;
|
|
31
|
+
/**
|
|
32
|
+
* Handle paste events.
|
|
33
|
+
*/
|
|
34
|
+
protected onPaste(event: ClipboardEvent): void;
|
|
35
|
+
/**
|
|
36
|
+
* Handle focus events.
|
|
37
|
+
*/
|
|
38
|
+
protected onFocus(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Handle blur events.
|
|
41
|
+
*/
|
|
42
|
+
protected onBlur(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Handle keyup events to update selection.
|
|
45
|
+
*/
|
|
46
|
+
protected onKeyup(): void;
|
|
47
|
+
/**
|
|
48
|
+
* Handle selection change events.
|
|
49
|
+
*/
|
|
50
|
+
protected onSelect(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Update the selection state.
|
|
53
|
+
*/
|
|
54
|
+
private updateSelection;
|
|
55
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NgpInputOtpInput, never>;
|
|
56
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgpInputOtpInput, "input[ngpInputOtpInput]", ["ngpInputOtpInput"], {}, {}, never, never, true, [{ directive: typeof i1.NgpVisuallyHidden; inputs: {}; outputs: {}; }]>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { OnDestroy } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
export declare class NgpInputOtpSlot implements OnDestroy {
|
|
4
|
+
/**
|
|
5
|
+
* Access the input-otp state.
|
|
6
|
+
*/
|
|
7
|
+
protected readonly state: import("@angular/core").Signal<import("ng-primitives/state").State<import("ng-primitives/input-otp").NgpInputOtp>>;
|
|
8
|
+
/**
|
|
9
|
+
* The computed index of this slot based on registration order.
|
|
10
|
+
*/
|
|
11
|
+
readonly index: import("@angular/core").Signal<number>;
|
|
12
|
+
/**
|
|
13
|
+
* The character for this slot from the value string.
|
|
14
|
+
*/
|
|
15
|
+
private readonly char;
|
|
16
|
+
/**
|
|
17
|
+
* Whether this slot is focused (active).
|
|
18
|
+
*/
|
|
19
|
+
protected readonly focused: import("@angular/core").Signal<boolean>;
|
|
20
|
+
/**
|
|
21
|
+
* Whether this slot should show the caret.
|
|
22
|
+
*/
|
|
23
|
+
protected readonly caret: import("@angular/core").Signal<boolean>;
|
|
24
|
+
/**
|
|
25
|
+
* Whether this slot is filled with a character.
|
|
26
|
+
*/
|
|
27
|
+
protected readonly filled: import("@angular/core").Signal<boolean>;
|
|
28
|
+
/**
|
|
29
|
+
* Whether to show placeholder for this slot.
|
|
30
|
+
*/
|
|
31
|
+
protected readonly showPlaceholder: import("@angular/core").Signal<boolean>;
|
|
32
|
+
/**
|
|
33
|
+
* The display character for this slot (character or placeholder).
|
|
34
|
+
*/
|
|
35
|
+
protected readonly displayChar: import("@angular/core").Signal<string>;
|
|
36
|
+
constructor();
|
|
37
|
+
ngOnDestroy(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Handle click events on the slot.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
protected onClick(event: Event): void;
|
|
43
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NgpInputOtpSlot, never>;
|
|
44
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<NgpInputOtpSlot, "[ngpInputOtpSlot]", ["ngpInputOtpSlot"], {}, {}, never, never, true, never>;
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "ng-primitives",
|
|
3
3
|
"description": "Angular Primitives is a low-level headless UI component library with a focus on accessibility, customization, and developer experience. ",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.88.0",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"angular",
|
|
8
8
|
"primitives",
|
|
@@ -75,14 +75,14 @@
|
|
|
75
75
|
"types": "./ai/index.d.ts",
|
|
76
76
|
"default": "./fesm2022/ng-primitives-ai.mjs"
|
|
77
77
|
},
|
|
78
|
-
"./avatar": {
|
|
79
|
-
"types": "./avatar/index.d.ts",
|
|
80
|
-
"default": "./fesm2022/ng-primitives-avatar.mjs"
|
|
81
|
-
},
|
|
82
78
|
"./autofill": {
|
|
83
79
|
"types": "./autofill/index.d.ts",
|
|
84
80
|
"default": "./fesm2022/ng-primitives-autofill.mjs"
|
|
85
81
|
},
|
|
82
|
+
"./avatar": {
|
|
83
|
+
"types": "./avatar/index.d.ts",
|
|
84
|
+
"default": "./fesm2022/ng-primitives-avatar.mjs"
|
|
85
|
+
},
|
|
86
86
|
"./button": {
|
|
87
87
|
"types": "./button/index.d.ts",
|
|
88
88
|
"default": "./fesm2022/ng-primitives-button.mjs"
|
|
@@ -103,14 +103,14 @@
|
|
|
103
103
|
"types": "./date-picker/index.d.ts",
|
|
104
104
|
"default": "./fesm2022/ng-primitives-date-picker.mjs"
|
|
105
105
|
},
|
|
106
|
-
"./date-time-luxon": {
|
|
107
|
-
"types": "./date-time-luxon/index.d.ts",
|
|
108
|
-
"default": "./fesm2022/ng-primitives-date-time-luxon.mjs"
|
|
109
|
-
},
|
|
110
106
|
"./date-time": {
|
|
111
107
|
"types": "./date-time/index.d.ts",
|
|
112
108
|
"default": "./fesm2022/ng-primitives-date-time.mjs"
|
|
113
109
|
},
|
|
110
|
+
"./date-time-luxon": {
|
|
111
|
+
"types": "./date-time-luxon/index.d.ts",
|
|
112
|
+
"default": "./fesm2022/ng-primitives-date-time-luxon.mjs"
|
|
113
|
+
},
|
|
114
114
|
"./dialog": {
|
|
115
115
|
"types": "./dialog/index.d.ts",
|
|
116
116
|
"default": "./fesm2022/ng-primitives-dialog.mjs"
|
|
@@ -131,6 +131,10 @@
|
|
|
131
131
|
"types": "./input/index.d.ts",
|
|
132
132
|
"default": "./fesm2022/ng-primitives-input.mjs"
|
|
133
133
|
},
|
|
134
|
+
"./input-otp": {
|
|
135
|
+
"types": "./input-otp/index.d.ts",
|
|
136
|
+
"default": "./fesm2022/ng-primitives-input-otp.mjs"
|
|
137
|
+
},
|
|
134
138
|
"./interactions": {
|
|
135
139
|
"types": "./interactions/index.d.ts",
|
|
136
140
|
"default": "./fesm2022/ng-primitives-interactions.mjs"
|
|
@@ -155,14 +159,14 @@
|
|
|
155
159
|
"types": "./pagination/index.d.ts",
|
|
156
160
|
"default": "./fesm2022/ng-primitives-pagination.mjs"
|
|
157
161
|
},
|
|
158
|
-
"./popover": {
|
|
159
|
-
"types": "./popover/index.d.ts",
|
|
160
|
-
"default": "./fesm2022/ng-primitives-popover.mjs"
|
|
161
|
-
},
|
|
162
162
|
"./portal": {
|
|
163
163
|
"types": "./portal/index.d.ts",
|
|
164
164
|
"default": "./fesm2022/ng-primitives-portal.mjs"
|
|
165
165
|
},
|
|
166
|
+
"./popover": {
|
|
167
|
+
"types": "./popover/index.d.ts",
|
|
168
|
+
"default": "./fesm2022/ng-primitives-popover.mjs"
|
|
169
|
+
},
|
|
166
170
|
"./progress": {
|
|
167
171
|
"types": "./progress/index.d.ts",
|
|
168
172
|
"default": "./fesm2022/ng-primitives-progress.mjs"
|
|
@@ -183,10 +187,6 @@
|
|
|
183
187
|
"types": "./search/index.d.ts",
|
|
184
188
|
"default": "./fesm2022/ng-primitives-search.mjs"
|
|
185
189
|
},
|
|
186
|
-
"./select": {
|
|
187
|
-
"types": "./select/index.d.ts",
|
|
188
|
-
"default": "./fesm2022/ng-primitives-select.mjs"
|
|
189
|
-
},
|
|
190
190
|
"./separator": {
|
|
191
191
|
"types": "./separator/index.d.ts",
|
|
192
192
|
"default": "./fesm2022/ng-primitives-separator.mjs"
|
|
@@ -215,14 +215,18 @@
|
|
|
215
215
|
"types": "./toast/index.d.ts",
|
|
216
216
|
"default": "./fesm2022/ng-primitives-toast.mjs"
|
|
217
217
|
},
|
|
218
|
-
"./
|
|
219
|
-
"types": "./
|
|
220
|
-
"default": "./fesm2022/ng-primitives-
|
|
218
|
+
"./select": {
|
|
219
|
+
"types": "./select/index.d.ts",
|
|
220
|
+
"default": "./fesm2022/ng-primitives-select.mjs"
|
|
221
221
|
},
|
|
222
222
|
"./toggle-group": {
|
|
223
223
|
"types": "./toggle-group/index.d.ts",
|
|
224
224
|
"default": "./fesm2022/ng-primitives-toggle-group.mjs"
|
|
225
225
|
},
|
|
226
|
+
"./toggle": {
|
|
227
|
+
"types": "./toggle/index.d.ts",
|
|
228
|
+
"default": "./fesm2022/ng-primitives-toggle.mjs"
|
|
229
|
+
},
|
|
226
230
|
"./toolbar": {
|
|
227
231
|
"types": "./toolbar/index.d.ts",
|
|
228
232
|
"default": "./fesm2022/ng-primitives-toolbar.mjs"
|
|
@@ -30,7 +30,8 @@ export interface AngularPrimitivesComponentSchema {
|
|
|
30
30
|
| 'popover'
|
|
31
31
|
| 'combobox'
|
|
32
32
|
| 'select'
|
|
33
|
-
| 'native-select'
|
|
33
|
+
| 'native-select'
|
|
34
|
+
| 'input-otp';
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
37
|
* The path where the component files should be created, relative to the current workspace.
|
|
@@ -20,16 +20,17 @@
|
|
|
20
20
|
"file-upload",
|
|
21
21
|
"form-field",
|
|
22
22
|
"input",
|
|
23
|
+
"input-otp",
|
|
23
24
|
"listbox",
|
|
24
25
|
"menu",
|
|
25
26
|
"meter",
|
|
27
|
+
"native-select",
|
|
26
28
|
"pagination",
|
|
27
29
|
"popover",
|
|
28
30
|
"progress",
|
|
29
31
|
"radio",
|
|
30
32
|
"search",
|
|
31
33
|
"select",
|
|
32
|
-
"native-select",
|
|
33
34
|
"separator",
|
|
34
35
|
"slider",
|
|
35
36
|
"switch",
|
package/schematics/ng-generate/templates/input-otp/input-otp.__fileSuffix@dasherize__.ts.template
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { BooleanInput, NumberInput } from '@angular/cdk/coercion';
|
|
2
|
+
import {
|
|
3
|
+
booleanAttribute,
|
|
4
|
+
Component,
|
|
5
|
+
computed,
|
|
6
|
+
forwardRef,
|
|
7
|
+
input,
|
|
8
|
+
model,
|
|
9
|
+
numberAttribute,
|
|
10
|
+
signal,
|
|
11
|
+
} from '@angular/core';
|
|
12
|
+
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
13
|
+
import { NgpInputOtp, NgpInputOtpInput, NgpInputOtpSlot } from 'ng-primitives/input-otp';
|
|
14
|
+
import { ChangeFn, TouchedFn } from 'ng-primitives/utils';
|
|
15
|
+
|
|
16
|
+
@Component({
|
|
17
|
+
selector: '<%= prefix %>-input-otp',
|
|
18
|
+
imports: [NgpInputOtp, NgpInputOtpInput, NgpInputOtpSlot],
|
|
19
|
+
providers: [
|
|
20
|
+
{
|
|
21
|
+
provide: NG_VALUE_ACCESSOR,
|
|
22
|
+
useExisting: forwardRef(() => InputOtp<%= componentSuffix %>),
|
|
23
|
+
multi: true,
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
template: `
|
|
27
|
+
<div
|
|
28
|
+
[ngpInputOtpValue]="value()"
|
|
29
|
+
[ngpInputOtpDisabled]="disabled() || formDisabled()"
|
|
30
|
+
[ngpInputOtpPattern]="pattern()"
|
|
31
|
+
[ngpInputOtpPlaceholder]="placeholder()"
|
|
32
|
+
[ngpInputOtpInputMode]="inputMode()"
|
|
33
|
+
(ngpInputOtpValueChange)="onValueChange($event)"
|
|
34
|
+
(ngpInputOtpComplete)="onComplete()"
|
|
35
|
+
ngpInputOtp
|
|
36
|
+
>
|
|
37
|
+
<input ngpInputOtpInput />
|
|
38
|
+
<div class="slots">
|
|
39
|
+
@for (_ of slots(); track $index) {
|
|
40
|
+
<div class="slot" ngpInputOtpSlot></div>
|
|
41
|
+
}
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
`,
|
|
45
|
+
styles: `
|
|
46
|
+
/* These styles rely on CSS variables that can be imported from ng-primitives/example-theme/index.css in your global styles */
|
|
47
|
+
|
|
48
|
+
:host {
|
|
49
|
+
display: inline-flex;
|
|
50
|
+
flex-direction: column;
|
|
51
|
+
gap: 12px;
|
|
52
|
+
max-width: 100%;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.slots {
|
|
56
|
+
display: flex;
|
|
57
|
+
gap: 8px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.slot {
|
|
61
|
+
display: flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
width: 48px;
|
|
65
|
+
height: 48px;
|
|
66
|
+
border: 2px solid var(--ngp-border);
|
|
67
|
+
border-radius: 8px;
|
|
68
|
+
background: var(--ngp-background);
|
|
69
|
+
font-size: 18px;
|
|
70
|
+
font-weight: 600;
|
|
71
|
+
color: var(--ngp-text-primary);
|
|
72
|
+
transition: all 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
position: relative;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.slot[data-filled] {
|
|
78
|
+
border-color: var(--ngp-border-strong);
|
|
79
|
+
background: var(--ngp-background-accent);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.slot[data-active] {
|
|
83
|
+
border-color: var(--ngp-focus-ring);
|
|
84
|
+
box-shadow: 0 0 0 1px var(--ngp-focus-ring);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.slot[data-placeholder] {
|
|
88
|
+
color: var(--ngp-text-placeholder);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.slot[data-caret]::after {
|
|
92
|
+
content: '';
|
|
93
|
+
position: absolute;
|
|
94
|
+
width: 2px;
|
|
95
|
+
height: 20px;
|
|
96
|
+
background: var(--ngp-focus-ring);
|
|
97
|
+
animation: blink 1s infinite;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@keyframes blink {
|
|
101
|
+
0%,
|
|
102
|
+
50% {
|
|
103
|
+
opacity: 1;
|
|
104
|
+
}
|
|
105
|
+
51%,
|
|
106
|
+
100% {
|
|
107
|
+
opacity: 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
:host([data-disabled]) .slot {
|
|
112
|
+
background: var(--ngp-background-disabled);
|
|
113
|
+
color: var(--ngp-text-disabled);
|
|
114
|
+
border-color: var(--ngp-border-disabled);
|
|
115
|
+
cursor: not-allowed;
|
|
116
|
+
}
|
|
117
|
+
`,
|
|
118
|
+
})
|
|
119
|
+
export class InputOtp<%= componentSuffix %> implements ControlValueAccessor {
|
|
120
|
+
/**
|
|
121
|
+
* The number of slots to display.
|
|
122
|
+
*/
|
|
123
|
+
readonly length = input<number, NumberInput>(6, {
|
|
124
|
+
transform: numberAttribute,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Whether the input is disabled.
|
|
129
|
+
*/
|
|
130
|
+
readonly disabled = input<boolean, BooleanInput>(false, {
|
|
131
|
+
transform: booleanAttribute,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The pattern for allowed characters.
|
|
136
|
+
*/
|
|
137
|
+
readonly pattern = input('[0-9]');
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The placeholder character for empty slots.
|
|
141
|
+
*/
|
|
142
|
+
readonly placeholder = input('');
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The input mode for the hidden input.
|
|
146
|
+
*/
|
|
147
|
+
readonly inputMode = input<'numeric' | 'text' | 'decimal' | 'tel' | 'search' | 'email' | 'url'>(
|
|
148
|
+
'numeric',
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Create an array for tracking slots.
|
|
153
|
+
*/
|
|
154
|
+
protected readonly slots = computed(() => Array.from({ length: this.length() }, (_, i) => i));
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The current value.
|
|
158
|
+
*/
|
|
159
|
+
readonly value = model<string>('');
|
|
160
|
+
|
|
161
|
+
private onChange: ChangeFn<string> = () => {};
|
|
162
|
+
private onTouched: TouchedFn = () => {};
|
|
163
|
+
|
|
164
|
+
protected readonly formDisabled = signal(false);
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Handle value changes from the input-otp directive.
|
|
168
|
+
*/
|
|
169
|
+
onValueChange(value: string): void {
|
|
170
|
+
this.value.set(value);
|
|
171
|
+
this.onChange(value);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Handle completion events from the input-otp directive.
|
|
176
|
+
*/
|
|
177
|
+
onComplete(): void {
|
|
178
|
+
this.onTouched();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ControlValueAccessor implementation
|
|
182
|
+
writeValue(value: string): void {
|
|
183
|
+
this.value.set(value);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
registerOnChange(fn: ChangeFn<string>): void {
|
|
187
|
+
this.onChange = fn;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
registerOnTouched(fn: TouchedFn): void {
|
|
191
|
+
this.onTouched = fn;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
setDisabledState(isDisabled: boolean): void {
|
|
195
|
+
this.formDisabled.set(isDisabled);
|
|
196
|
+
}
|
|
197
|
+
}
|