ng-primitives 0.87.0 → 0.89.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.
Files changed (29) hide show
  1. package/breadcrumbs/README.md +3 -0
  2. package/breadcrumbs/breadcrumb-ellipsis/breadcrumb-ellipsis.d.ts +8 -0
  3. package/breadcrumbs/breadcrumb-item/breadcrumb-item.d.ts +8 -0
  4. package/breadcrumbs/breadcrumb-link/breadcrumb-link.d.ts +9 -0
  5. package/breadcrumbs/breadcrumb-list/breadcrumb-list.d.ts +8 -0
  6. package/breadcrumbs/breadcrumb-page/breadcrumb-page.d.ts +8 -0
  7. package/breadcrumbs/breadcrumb-separator/breadcrumb-separator.d.ts +8 -0
  8. package/breadcrumbs/breadcrumbs/breadcrumbs.d.ts +8 -0
  9. package/breadcrumbs/index.d.ts +7 -0
  10. package/date-picker/date-picker/date-picker.d.ts +2 -1
  11. package/date-picker/date-range-picker/date-range-picker.d.ts +3 -1
  12. package/fesm2022/ng-primitives-breadcrumbs.mjs +138 -0
  13. package/fesm2022/ng-primitives-breadcrumbs.mjs.map +1 -0
  14. package/fesm2022/ng-primitives-combobox.mjs +6 -0
  15. package/fesm2022/ng-primitives-combobox.mjs.map +1 -1
  16. package/fesm2022/ng-primitives-date-picker.mjs +46 -15
  17. package/fesm2022/ng-primitives-date-picker.mjs.map +1 -1
  18. package/fesm2022/ng-primitives-input-otp.mjs +514 -0
  19. package/fesm2022/ng-primitives-input-otp.mjs.map +1 -0
  20. package/input-otp/README.md +3 -0
  21. package/input-otp/index.d.ts +4 -0
  22. package/input-otp/input-otp/input-otp-state.d.ts +17 -0
  23. package/input-otp/input-otp/input-otp.d.ts +133 -0
  24. package/input-otp/input-otp-input/input-otp-input.d.ts +57 -0
  25. package/input-otp/input-otp-slot/input-otp-slot.d.ts +45 -0
  26. package/package.json +32 -24
  27. package/schematics/ng-generate/schema.d.ts +2 -1
  28. package/schematics/ng-generate/schema.json +2 -1
  29. 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.87.0",
5
+ "version": "0.89.0",
6
6
  "keywords": [
7
7
  "angular",
8
8
  "primitives",
@@ -71,10 +71,6 @@
71
71
  "types": "./accordion/index.d.ts",
72
72
  "default": "./fesm2022/ng-primitives-accordion.mjs"
73
73
  },
74
- "./ai": {
75
- "types": "./ai/index.d.ts",
76
- "default": "./fesm2022/ng-primitives-ai.mjs"
77
- },
78
74
  "./avatar": {
79
75
  "types": "./avatar/index.d.ts",
80
76
  "default": "./fesm2022/ng-primitives-avatar.mjs"
@@ -83,6 +79,14 @@
83
79
  "types": "./autofill/index.d.ts",
84
80
  "default": "./fesm2022/ng-primitives-autofill.mjs"
85
81
  },
82
+ "./breadcrumbs": {
83
+ "types": "./breadcrumbs/index.d.ts",
84
+ "default": "./fesm2022/ng-primitives-breadcrumbs.mjs"
85
+ },
86
+ "./ai": {
87
+ "types": "./ai/index.d.ts",
88
+ "default": "./fesm2022/ng-primitives-ai.mjs"
89
+ },
86
90
  "./button": {
87
91
  "types": "./button/index.d.ts",
88
92
  "default": "./fesm2022/ng-primitives-button.mjs"
@@ -95,22 +99,22 @@
95
99
  "types": "./combobox/index.d.ts",
96
100
  "default": "./fesm2022/ng-primitives-combobox.mjs"
97
101
  },
98
- "./common": {
99
- "types": "./common/index.d.ts",
100
- "default": "./fesm2022/ng-primitives-common.mjs"
101
- },
102
102
  "./date-picker": {
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"
106
+ "./common": {
107
+ "types": "./common/index.d.ts",
108
+ "default": "./fesm2022/ng-primitives-common.mjs"
109
109
  },
110
110
  "./date-time": {
111
111
  "types": "./date-time/index.d.ts",
112
112
  "default": "./fesm2022/ng-primitives-date-time.mjs"
113
113
  },
114
+ "./date-time-luxon": {
115
+ "types": "./date-time-luxon/index.d.ts",
116
+ "default": "./fesm2022/ng-primitives-date-time-luxon.mjs"
117
+ },
114
118
  "./dialog": {
115
119
  "types": "./dialog/index.d.ts",
116
120
  "default": "./fesm2022/ng-primitives-dialog.mjs"
@@ -131,6 +135,10 @@
131
135
  "types": "./input/index.d.ts",
132
136
  "default": "./fesm2022/ng-primitives-input.mjs"
133
137
  },
138
+ "./input-otp": {
139
+ "types": "./input-otp/index.d.ts",
140
+ "default": "./fesm2022/ng-primitives-input-otp.mjs"
141
+ },
134
142
  "./interactions": {
135
143
  "types": "./interactions/index.d.ts",
136
144
  "default": "./fesm2022/ng-primitives-interactions.mjs"
@@ -147,14 +155,14 @@
147
155
  "types": "./menu/index.d.ts",
148
156
  "default": "./fesm2022/ng-primitives-menu.mjs"
149
157
  },
150
- "./meter": {
151
- "types": "./meter/index.d.ts",
152
- "default": "./fesm2022/ng-primitives-meter.mjs"
153
- },
154
158
  "./pagination": {
155
159
  "types": "./pagination/index.d.ts",
156
160
  "default": "./fesm2022/ng-primitives-pagination.mjs"
157
161
  },
162
+ "./meter": {
163
+ "types": "./meter/index.d.ts",
164
+ "default": "./fesm2022/ng-primitives-meter.mjs"
165
+ },
158
166
  "./popover": {
159
167
  "types": "./popover/index.d.ts",
160
168
  "default": "./fesm2022/ng-primitives-popover.mjs"
@@ -171,14 +179,14 @@
171
179
  "types": "./radio/index.d.ts",
172
180
  "default": "./fesm2022/ng-primitives-radio.mjs"
173
181
  },
174
- "./resize": {
175
- "types": "./resize/index.d.ts",
176
- "default": "./fesm2022/ng-primitives-resize.mjs"
177
- },
178
182
  "./roving-focus": {
179
183
  "types": "./roving-focus/index.d.ts",
180
184
  "default": "./fesm2022/ng-primitives-roving-focus.mjs"
181
185
  },
186
+ "./resize": {
187
+ "types": "./resize/index.d.ts",
188
+ "default": "./fesm2022/ng-primitives-resize.mjs"
189
+ },
182
190
  "./search": {
183
191
  "types": "./search/index.d.ts",
184
192
  "default": "./fesm2022/ng-primitives-search.mjs"
@@ -211,10 +219,6 @@
211
219
  "types": "./textarea/index.d.ts",
212
220
  "default": "./fesm2022/ng-primitives-textarea.mjs"
213
221
  },
214
- "./toast": {
215
- "types": "./toast/index.d.ts",
216
- "default": "./fesm2022/ng-primitives-toast.mjs"
217
- },
218
222
  "./toggle": {
219
223
  "types": "./toggle/index.d.ts",
220
224
  "default": "./fesm2022/ng-primitives-toggle.mjs"
@@ -223,6 +227,10 @@
223
227
  "types": "./toggle-group/index.d.ts",
224
228
  "default": "./fesm2022/ng-primitives-toggle-group.mjs"
225
229
  },
230
+ "./toast": {
231
+ "types": "./toast/index.d.ts",
232
+ "default": "./fesm2022/ng-primitives-toast.mjs"
233
+ },
226
234
  "./toolbar": {
227
235
  "types": "./toolbar/index.d.ts",
228
236
  "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",
@@ -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
+ }