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.
@@ -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.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
- "./toggle": {
219
- "types": "./toggle/index.d.ts",
220
- "default": "./fesm2022/ng-primitives-toggle.mjs"
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",
@@ -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
+ }