ng-primitives 0.86.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,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
+ }