@aquera/nile-elements 1.6.1 → 1.6.2

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 (174) hide show
  1. package/README.md +6 -0
  2. package/dist/index.cjs.js +1 -1
  3. package/dist/index.esm.js +1 -1
  4. package/dist/index.js +953 -595
  5. package/dist/internal/enum.cjs.js +1 -1
  6. package/dist/internal/enum.cjs.js.map +1 -1
  7. package/dist/internal/enum.esm.js +1 -1
  8. package/dist/nile-badge/index.cjs.js +1 -1
  9. package/dist/nile-badge/index.esm.js +1 -1
  10. package/dist/nile-badge/nile-badge.cjs.js +1 -1
  11. package/dist/nile-badge/nile-badge.cjs.js.map +1 -1
  12. package/dist/nile-badge/nile-badge.esm.js +1 -1
  13. package/dist/nile-button/index.cjs.js +1 -1
  14. package/dist/nile-button/index.esm.js +1 -1
  15. package/dist/nile-button/nile-button.cjs.js +1 -1
  16. package/dist/nile-button/nile-button.cjs.js.map +1 -1
  17. package/dist/nile-button/nile-button.esm.js +1 -1
  18. package/dist/nile-carousel/index.cjs.js +1 -1
  19. package/dist/nile-carousel/index.esm.js +1 -1
  20. package/dist/nile-carousel/nile-carousel.cjs.js +1 -1
  21. package/dist/nile-carousel/nile-carousel.cjs.js.map +1 -1
  22. package/dist/nile-carousel/nile-carousel.esm.js +1 -1
  23. package/dist/nile-dialog/index.cjs.js +1 -1
  24. package/dist/nile-dialog/index.esm.js +1 -1
  25. package/dist/nile-dialog/nile-dialog.cjs.js +1 -1
  26. package/dist/nile-dialog/nile-dialog.cjs.js.map +1 -1
  27. package/dist/nile-dialog/nile-dialog.esm.js +1 -1
  28. package/dist/nile-drawer/index.cjs.js +1 -1
  29. package/dist/nile-drawer/index.esm.js +1 -1
  30. package/dist/nile-drawer/nile-drawer.cjs.js +1 -1
  31. package/dist/nile-drawer/nile-drawer.cjs.js.map +1 -1
  32. package/dist/nile-drawer/nile-drawer.esm.js +1 -1
  33. package/dist/nile-floating-panel/nile-floating-panel.cjs.js +1 -1
  34. package/dist/nile-floating-panel/nile-floating-panel.cjs.js.map +1 -1
  35. package/dist/nile-floating-panel/nile-floating-panel.esm.js +1 -1
  36. package/dist/nile-icon/icons/svg/folder_delete.cjs.js +2 -0
  37. package/dist/nile-icon/icons/svg/folder_delete.cjs.js.map +1 -0
  38. package/dist/nile-icon/icons/svg/folder_delete.esm.js +1 -0
  39. package/dist/nile-icon/icons/svg/index.cjs.js +1 -1
  40. package/dist/nile-icon/icons/svg/index.esm.js +1 -1
  41. package/dist/nile-icon/icons/svg/layers-three-02.cjs.js +1 -1
  42. package/dist/nile-icon/icons/svg/layers-three-02.cjs.js.map +1 -1
  43. package/dist/nile-icon/icons/svg/layers-three-02.esm.js +1 -1
  44. package/dist/nile-icon/index.cjs.js +1 -1
  45. package/dist/nile-icon/index.cjs.js.map +1 -1
  46. package/dist/nile-icon/index.esm.js +1 -1
  47. package/dist/nile-icon-button/index.cjs.js +1 -1
  48. package/dist/nile-icon-button/index.esm.js +1 -1
  49. package/dist/nile-icon-button/nile-icon-button.cjs.js +1 -1
  50. package/dist/nile-icon-button/nile-icon-button.cjs.js.map +1 -1
  51. package/dist/nile-icon-button/nile-icon-button.esm.js +1 -1
  52. package/dist/nile-input/index.cjs.js +1 -1
  53. package/dist/nile-input/index.esm.js +1 -1
  54. package/dist/nile-input/nile-input.cjs.js +1 -1
  55. package/dist/nile-input/nile-input.cjs.js.map +1 -1
  56. package/dist/nile-input/nile-input.esm.js +1 -1
  57. package/dist/nile-menu-item/index.cjs.js +1 -1
  58. package/dist/nile-menu-item/index.esm.js +1 -1
  59. package/dist/nile-menu-item/nile-menu-item.cjs.js +1 -1
  60. package/dist/nile-menu-item/nile-menu-item.cjs.js.map +1 -1
  61. package/dist/nile-menu-item/nile-menu-item.esm.js +1 -1
  62. package/dist/nile-option/index.cjs.js +1 -1
  63. package/dist/nile-option/index.esm.js +1 -1
  64. package/dist/nile-option/nile-option.cjs.js +1 -1
  65. package/dist/nile-option/nile-option.cjs.js.map +1 -1
  66. package/dist/nile-option/nile-option.esm.js +1 -1
  67. package/dist/nile-otp-input/index.cjs.js +2 -0
  68. package/dist/nile-otp-input/index.cjs.js.map +1 -0
  69. package/dist/nile-otp-input/index.esm.js +1 -0
  70. package/dist/nile-otp-input/nile-otp-input.cjs.js +2 -0
  71. package/dist/nile-otp-input/nile-otp-input.cjs.js.map +1 -0
  72. package/dist/nile-otp-input/nile-otp-input.css.cjs.js +2 -0
  73. package/dist/nile-otp-input/nile-otp-input.css.cjs.js.map +1 -0
  74. package/dist/nile-otp-input/nile-otp-input.css.esm.js +257 -0
  75. package/dist/nile-otp-input/nile-otp-input.enum.cjs.js +2 -0
  76. package/dist/nile-otp-input/nile-otp-input.enum.cjs.js.map +1 -0
  77. package/dist/nile-otp-input/nile-otp-input.enum.esm.js +1 -0
  78. package/dist/nile-otp-input/nile-otp-input.esm.js +103 -0
  79. package/dist/nile-select/index.cjs.js +1 -1
  80. package/dist/nile-select/index.esm.js +1 -1
  81. package/dist/nile-select/nile-select.cjs.js +1 -1
  82. package/dist/nile-select/nile-select.cjs.js.map +1 -1
  83. package/dist/nile-select/nile-select.esm.js +1 -1
  84. package/dist/nile-side-bar-action-menu-item/index.cjs.js +1 -1
  85. package/dist/nile-side-bar-action-menu-item/index.esm.js +1 -1
  86. package/dist/nile-side-bar-action-menu-item/nile-side-bar-action-menu-item.cjs.js +1 -1
  87. package/dist/nile-side-bar-action-menu-item/nile-side-bar-action-menu-item.cjs.js.map +1 -1
  88. package/dist/nile-side-bar-action-menu-item/nile-side-bar-action-menu-item.esm.js +1 -1
  89. package/dist/nile-tab/index.cjs.js +1 -1
  90. package/dist/nile-tab/index.esm.js +1 -1
  91. package/dist/nile-tab/nile-tab.cjs.js +1 -1
  92. package/dist/nile-tab/nile-tab.cjs.js.map +1 -1
  93. package/dist/nile-tab/nile-tab.esm.js +1 -1
  94. package/dist/nile-tab-group/index.cjs.js +1 -1
  95. package/dist/nile-tab-group/index.esm.js +1 -1
  96. package/dist/nile-tab-group/nile-tab-group.cjs.js +1 -1
  97. package/dist/nile-tab-group/nile-tab-group.cjs.js.map +1 -1
  98. package/dist/nile-tab-group/nile-tab-group.esm.js +1 -1
  99. package/dist/nile-tag/index.cjs.js +1 -1
  100. package/dist/nile-tag/index.esm.js +1 -1
  101. package/dist/nile-tag/nile-tag.cjs.js +1 -1
  102. package/dist/nile-tag/nile-tag.cjs.js.map +1 -1
  103. package/dist/nile-tag/nile-tag.esm.js +1 -1
  104. package/dist/nile-toast/index.cjs.js +1 -1
  105. package/dist/nile-toast/index.esm.js +1 -1
  106. package/dist/nile-toast/nile-toast.cjs.js +1 -1
  107. package/dist/nile-toast/nile-toast.cjs.js.map +1 -1
  108. package/dist/nile-toast/nile-toast.esm.js +1 -1
  109. package/dist/nile-tree/index.cjs.js +1 -1
  110. package/dist/nile-tree/index.esm.js +1 -1
  111. package/dist/nile-tree/nile-tree.cjs.js +1 -1
  112. package/dist/nile-tree/nile-tree.cjs.js.map +1 -1
  113. package/dist/nile-tree/nile-tree.esm.js +1 -1
  114. package/dist/nile-tree-item/index.cjs.js +1 -1
  115. package/dist/nile-tree-item/index.esm.js +1 -1
  116. package/dist/nile-tree-item/nile-tree-item.cjs.js +1 -1
  117. package/dist/nile-tree-item/nile-tree-item.cjs.js.map +1 -1
  118. package/dist/nile-tree-item/nile-tree-item.esm.js +1 -1
  119. package/dist/nile-virtual-select/index.cjs.js +1 -1
  120. package/dist/nile-virtual-select/index.esm.js +1 -1
  121. package/dist/nile-virtual-select/nile-virtual-select.cjs.js +2 -2
  122. package/dist/nile-virtual-select/nile-virtual-select.esm.js +1 -1
  123. package/dist/src/index.d.ts +1 -0
  124. package/dist/src/index.js +1 -0
  125. package/dist/src/index.js.map +1 -1
  126. package/dist/src/internal/enum.d.ts +21 -0
  127. package/dist/src/internal/enum.js +23 -1
  128. package/dist/src/internal/enum.js.map +1 -1
  129. package/dist/src/nile-floating-panel/nile-floating-panel.d.ts +2 -0
  130. package/dist/src/nile-floating-panel/nile-floating-panel.js +12 -1
  131. package/dist/src/nile-floating-panel/nile-floating-panel.js.map +1 -1
  132. package/dist/src/nile-icon/icons/svg/folder_delete.d.ts +5 -0
  133. package/dist/src/nile-icon/icons/svg/folder_delete.js +5 -0
  134. package/dist/src/nile-icon/icons/svg/folder_delete.js.map +1 -0
  135. package/dist/src/nile-icon/icons/svg/index.d.ts +1 -0
  136. package/dist/src/nile-icon/icons/svg/index.js +1 -0
  137. package/dist/src/nile-icon/icons/svg/index.js.map +1 -1
  138. package/dist/src/nile-icon/icons/svg/layers-three-02.d.ts +1 -1
  139. package/dist/src/nile-icon/icons/svg/layers-three-02.js +1 -1
  140. package/dist/src/nile-icon/icons/svg/layers-three-02.js.map +1 -1
  141. package/dist/src/nile-otp-input/index.d.ts +1 -0
  142. package/dist/src/nile-otp-input/index.js +2 -0
  143. package/dist/src/nile-otp-input/index.js.map +1 -0
  144. package/dist/src/nile-otp-input/nile-otp-input.css.d.ts +12 -0
  145. package/dist/src/nile-otp-input/nile-otp-input.css.js +269 -0
  146. package/dist/src/nile-otp-input/nile-otp-input.css.js.map +1 -0
  147. package/dist/src/nile-otp-input/nile-otp-input.d.ts +156 -0
  148. package/dist/src/nile-otp-input/nile-otp-input.enum.d.ts +26 -0
  149. package/dist/src/nile-otp-input/nile-otp-input.enum.js +32 -0
  150. package/dist/src/nile-otp-input/nile-otp-input.enum.js.map +1 -0
  151. package/dist/src/nile-otp-input/nile-otp-input.js +762 -0
  152. package/dist/src/nile-otp-input/nile-otp-input.js.map +1 -0
  153. package/dist/src/nile-otp-input/nile-otp-input.test.d.ts +1 -0
  154. package/dist/src/nile-otp-input/nile-otp-input.test.js +493 -0
  155. package/dist/src/nile-otp-input/nile-otp-input.test.js.map +1 -0
  156. package/dist/src/version.js +2 -2
  157. package/dist/src/version.js.map +1 -1
  158. package/dist/tsconfig.tsbuildinfo +1 -1
  159. package/package.json +2 -1
  160. package/plop-templates/lit/index.ts.hbs +1 -1
  161. package/plop-templates/lit/lit.css.ts.hbs +1 -1
  162. package/plop-templates/lit/lit.ts.hbs +1 -1
  163. package/src/index.ts +2 -1
  164. package/src/internal/enum.ts +23 -1
  165. package/src/nile-floating-panel/nile-floating-panel.ts +10 -1
  166. package/src/nile-icon/icons/svg/folder_delete.ts +5 -0
  167. package/src/nile-icon/icons/svg/index.ts +1 -0
  168. package/src/nile-icon/icons/svg/layers-three-02.ts +1 -1
  169. package/src/nile-otp-input/index.ts +1 -0
  170. package/src/nile-otp-input/nile-otp-input.css.ts +271 -0
  171. package/src/nile-otp-input/nile-otp-input.enum.ts +30 -0
  172. package/src/nile-otp-input/nile-otp-input.test.ts +732 -0
  173. package/src/nile-otp-input/nile-otp-input.ts +835 -0
  174. package/vscode-html-custom-data.json +171 -1
@@ -0,0 +1,762 @@
1
+ /**
2
+ * Copyright Aquera Inc 2026
3
+ *
4
+ * This source code is licensed under the BSD-3-Clause license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { __decorate } from "tslib";
8
+ import { html } from 'lit';
9
+ import { customElement, property, query, queryAll, state, } from 'lit/decorators.js';
10
+ import { classMap } from 'lit/directives/class-map.js';
11
+ import { ifDefined } from 'lit/directives/if-defined.js';
12
+ import { live } from 'lit/directives/live.js';
13
+ import { defaultValue } from '../internal/default-value';
14
+ import { FormControlController, validValidityState } from '../internal/form';
15
+ import { HasSlotController } from '../internal/slot';
16
+ import { watch } from '../internal/watch';
17
+ import NileElement from '../internal/nile-element';
18
+ import { KeyCode, Nile_Events } from '../internal/enum';
19
+ import { styles } from './nile-otp-input.css';
20
+ import { OtpInputMode, OtpInputType, OtpEnterKeyHint, OtpAutoComplete, OtpCellPattern, } from './nile-otp-input.enum';
21
+ import '../nile-form-help-text';
22
+ import '../nile-form-error-message';
23
+ /**
24
+ * @summary OTP input renders a segmented set of cells but behaves like a single logical form control.
25
+ * @tag nile-otp-input
26
+ *
27
+ * @slot label - The input label. Alternatively, use the `label` attribute.
28
+ * @slot help-text - Helpful guidance text. Alternatively, use the `help-text` attribute.
29
+ *
30
+ * @event nile-input - Emitted whenever the OTP value changes from user input.
31
+ * @event nile-change - Emitted whenever the OTP value changes from user input.
32
+ * @event nile-complete - Emitted when all OTP cells are filled.
33
+ * @event nile-focus - Emitted when focus enters the component.
34
+ * @event nile-blur - Emitted when focus leaves the component.
35
+ * @event nile-paste - Emitted when OTP text is pasted.
36
+ * @event nile-invalid - Emitted when the control is invalid.
37
+ *
38
+ * @csspart form-control - Wrapper for label, input, and help/error content.
39
+ * @csspart form-control-label - Label wrapper.
40
+ * @csspart form-control-input - Input wrapper.
41
+ * @csspart form-control-help-text - Help text wrapper.
42
+ * @csspart form-control-error-message - Error message wrapper.
43
+ * @csspart base - OTP cell container.
44
+ * @csspart cell - Individual OTP cell.
45
+ * @csspart separator - Separator element between cell groups.
46
+ */
47
+ let NileOtpInput = class NileOtpInput extends NileElement {
48
+ constructor() {
49
+ super(...arguments);
50
+ this.formControlController = new FormControlController(this, {
51
+ assumeInteractionOn: [Nile_Events.NILE_BLUR, Nile_Events.NILE_INPUT],
52
+ });
53
+ this.hasSlotController = new HasSlotController(this, 'help-text', 'label');
54
+ this.customValidationMessage = '';
55
+ this.wasComplete = false;
56
+ this.hasFocus = false;
57
+ this.activeIndex = -1;
58
+ this.cells = this.createCells('');
59
+ /** The name of the input, submitted as a name/value pair with form data. */
60
+ this.name = '';
61
+ /** The current value of the OTP control. */
62
+ this.value = '';
63
+ /** The default value of the form control. Primarily used for resetting the form control. */
64
+ this.defaultValue = '';
65
+ /** Number of OTP cells. Values below 4 are clamped to 4. */
66
+ this.length = 6;
67
+ /** Restricts input to numeric digits when true. Overridden by `alphanumeric`. */
68
+ this.numericOnly = true;
69
+ /** Allows both letters and digits. When present, overrides `numeric-only`. */
70
+ this.alphanumeric = false;
71
+ /** The input's label. */
72
+ this.label = '';
73
+ this.helpText = '';
74
+ this.errorMessage = '';
75
+ /** Placeholder shown inside each OTP cell. */
76
+ this.placeholder = '';
77
+ /** Optional separator text rendered between configured OTP groups (for example "-"). */
78
+ this.separator = '';
79
+ /** Renders a separator after each N cells when `separator` is set. */
80
+ this.separatorEvery = 0;
81
+ /** Comma-separated zero-based cell indexes after which separators are rendered. */
82
+ this.separatorPositions = '';
83
+ /** Masks filled cells with dots, showing each character briefly while typing. */
84
+ this.masked = false;
85
+ /** Sets the input to a warning state, changing its visual appearance. */
86
+ this.warning = false;
87
+ /** Sets the input to an error state, changing its visual appearance. */
88
+ this.error = false;
89
+ /** Sets the input to a success state, changing its visual appearance. */
90
+ this.success = false;
91
+ /** Disables the control. */
92
+ this.disabled = false;
93
+ /** Makes the control readonly. */
94
+ this.readonly = false;
95
+ /**
96
+ * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you
97
+ * to place the form control outside of a form and associate it with the form that has this `id`.
98
+ */
99
+ this.form = '';
100
+ /** Makes this field required. */
101
+ this.required = false;
102
+ /** Indicates that the input should receive focus on page load. */
103
+ this.autofocus = false;
104
+ /** The autocomplete mode used on the first OTP cell. */
105
+ this.autocomplete = OtpAutoComplete.ONE_TIME_CODE;
106
+ }
107
+ connectedCallback() {
108
+ super.connectedCallback();
109
+ this.emit(Nile_Events.NILE_INIT);
110
+ }
111
+ disconnectedCallback() {
112
+ super.disconnectedCallback();
113
+ this.emit(Nile_Events.NILE_DESTROY);
114
+ }
115
+ firstUpdated() {
116
+ const normalized = this.normalizeValue(this.value);
117
+ if (normalized !== this.value) {
118
+ this.value = normalized;
119
+ return;
120
+ }
121
+ this.syncCellsFromValue(normalized);
122
+ this.wasComplete = this.isComplete(normalized);
123
+ this.valueInput.setCustomValidity(this.customValidationMessage);
124
+ this.formControlController.updateValidity();
125
+ if (this.autofocus) {
126
+ this.focus();
127
+ }
128
+ }
129
+ /** Gets the validity state object. */
130
+ get validity() {
131
+ return this.valueInput?.validity ?? validValidityState;
132
+ }
133
+ /** Gets the validation message. */
134
+ get validationMessage() {
135
+ return this.valueInput?.validationMessage ?? '';
136
+ }
137
+ /** Returns true when all OTP cells have values. */
138
+ get complete() {
139
+ return this.isComplete(this.value);
140
+ }
141
+ getNormalizedLength() {
142
+ const parsed = Number.isFinite(this.length) ? Math.trunc(this.length) : 6;
143
+ return Math.max(4, parsed);
144
+ }
145
+ isNumericMode() {
146
+ return this.numericOnly && !this.alphanumeric;
147
+ }
148
+ getResolvedInputMode() {
149
+ return this.inputmode ?? (this.isNumericMode() ? OtpInputMode.NUMERIC : OtpInputMode.TEXT);
150
+ }
151
+ getValidationPattern() {
152
+ if (this.pattern) {
153
+ return this.pattern;
154
+ }
155
+ const normalizedLength = this.getNormalizedLength();
156
+ return this.isNumericMode()
157
+ ? `[0-9]{${normalizedLength}}`
158
+ : `[A-Za-z0-9]{${normalizedLength}}`;
159
+ }
160
+ isAllowedCharacter(char) {
161
+ return this.isNumericMode() ? /^[0-9]$/.test(char) : /^[A-Za-z0-9]$/.test(char);
162
+ }
163
+ toOtpCharacters(value) {
164
+ return Array.from(value ?? '').filter(char => this.isAllowedCharacter(char));
165
+ }
166
+ normalizeValue(value) {
167
+ return this.toOtpCharacters(value ?? '')
168
+ .slice(0, this.getNormalizedLength())
169
+ .join('');
170
+ }
171
+ createCells(value) {
172
+ const normalizedLength = this.getNormalizedLength();
173
+ const normalizedChars = this.toOtpCharacters(value).slice(0, normalizedLength);
174
+ return Array.from({ length: normalizedLength }, (_, index) => normalizedChars[index] ?? '');
175
+ }
176
+ syncCellsFromValue(value) {
177
+ this.cells = this.createCells(value);
178
+ }
179
+ isComplete(value) {
180
+ return value.length === this.getNormalizedLength();
181
+ }
182
+ getFirstEmptyIndex() {
183
+ const index = this.cells.findIndex(char => char === '');
184
+ return index === -1 ? this.getNormalizedLength() - 1 : index;
185
+ }
186
+ getSeparatorIndices() {
187
+ const maxIndex = this.getNormalizedLength() - 2;
188
+ const indices = new Set();
189
+ if (Number.isInteger(this.separatorEvery) && this.separatorEvery > 0) {
190
+ for (let index = this.separatorEvery - 1; index <= maxIndex; index += this.separatorEvery) {
191
+ indices.add(index);
192
+ }
193
+ }
194
+ if (this.separatorPositions.trim().length > 0) {
195
+ this.separatorPositions
196
+ .split(',')
197
+ .map(value => Number.parseInt(value.trim(), 10))
198
+ .filter(index => Number.isInteger(index) && index >= 0 && index <= maxIndex)
199
+ .forEach(index => indices.add(index));
200
+ }
201
+ return indices;
202
+ }
203
+ getCellPlaceholder(index) {
204
+ if (!this.placeholder || this.cells[index]) {
205
+ return undefined;
206
+ }
207
+ if (this.activeIndex === -1) {
208
+ return index === this.getFirstEmptyIndex() ? this.placeholder : undefined;
209
+ }
210
+ return index === this.activeIndex ? this.placeholder : undefined;
211
+ }
212
+ focusCell(index, options) {
213
+ const input = this.cellInputs?.[index];
214
+ if (input) {
215
+ input.focus(options);
216
+ input.select();
217
+ }
218
+ }
219
+ updateCell(index, value) {
220
+ const nextCells = [...this.cells];
221
+ nextCells[index] = value;
222
+ this.cells = nextCells;
223
+ }
224
+ fillFromIndex(startIndex, chars) {
225
+ const nextCells = [...this.cells];
226
+ let cursor = startIndex;
227
+ for (const char of chars) {
228
+ if (cursor >= nextCells.length) {
229
+ break;
230
+ }
231
+ nextCells[cursor] = char;
232
+ cursor += 1;
233
+ }
234
+ this.cells = nextCells;
235
+ return cursor;
236
+ }
237
+ commitUserValueUpdate() {
238
+ const previousValue = this.value;
239
+ const nextValue = this.cells.join('');
240
+ const isNowComplete = this.isComplete(nextValue);
241
+ this.value = nextValue;
242
+ this.valueInput.value = nextValue;
243
+ this.valueInput.setCustomValidity(this.customValidationMessage);
244
+ this.formControlController.updateValidity();
245
+ this.emit(Nile_Events.NILE_INPUT, { value: nextValue, complete: isNowComplete });
246
+ if (previousValue !== nextValue) {
247
+ this.emit(Nile_Events.NILE_CHANGE, { value: nextValue, complete: isNowComplete });
248
+ }
249
+ if (isNowComplete && !this.wasComplete) {
250
+ this.emit(Nile_Events.NILE_COMPLETE, { value: nextValue });
251
+ }
252
+ this.wasComplete = isNowComplete;
253
+ }
254
+ handleInvalid(event) {
255
+ this.formControlController.setValidity(false);
256
+ this.formControlController.emitInvalidEvent(event);
257
+ }
258
+ handleCellFocus(event) {
259
+ const target = event.target;
260
+ const index = Number(target.dataset.index ?? -1);
261
+ const firstEmpty = this.getFirstEmptyIndex();
262
+ if (index > firstEmpty) {
263
+ this.focusCell(firstEmpty);
264
+ return;
265
+ }
266
+ if (index < firstEmpty && !this.cells[index]) {
267
+ this.focusCell(firstEmpty);
268
+ return;
269
+ }
270
+ this.activeIndex = index;
271
+ target.select();
272
+ if (!this.hasFocus) {
273
+ this.hasFocus = true;
274
+ this.emit(Nile_Events.NILE_FOCUS, { value: this.value });
275
+ }
276
+ }
277
+ handleCellBlur() {
278
+ queueMicrotask(() => {
279
+ const active = this.shadowRoot?.activeElement;
280
+ const isInsideOtp = active instanceof HTMLInputElement &&
281
+ active.classList.contains('otp__cell');
282
+ if (!isInsideOtp && this.hasFocus) {
283
+ this.hasFocus = false;
284
+ this.activeIndex = -1;
285
+ this.emit(Nile_Events.NILE_BLUR, { value: this.value });
286
+ }
287
+ });
288
+ }
289
+ handleCellInput(event) {
290
+ const target = event.target;
291
+ const index = Number(target.dataset.index ?? 0);
292
+ if (this.disabled || this.readonly) {
293
+ target.value = this.cells[index] ?? '';
294
+ return;
295
+ }
296
+ const chars = this.toOtpCharacters(target.value);
297
+ if (chars.length === 0) {
298
+ this.updateCell(index, '');
299
+ this.commitUserValueUpdate();
300
+ return;
301
+ }
302
+ if (chars.length === 1) {
303
+ this.updateCell(index, chars[0]);
304
+ this.commitUserValueUpdate();
305
+ const nextEmpty = this.getFirstEmptyIndex();
306
+ this.focusCell(nextEmpty);
307
+ return;
308
+ }
309
+ const nextCursor = this.fillFromIndex(index, chars);
310
+ this.commitUserValueUpdate();
311
+ const nextEmpty = this.getFirstEmptyIndex();
312
+ this.focusCell(nextEmpty);
313
+ }
314
+ handleCellPaste(event) {
315
+ if (this.disabled || this.readonly) {
316
+ return;
317
+ }
318
+ const pasted = event.clipboardData?.getData('text') ?? '';
319
+ const chars = this.toOtpCharacters(pasted);
320
+ if (!chars.length) {
321
+ return;
322
+ }
323
+ event.preventDefault();
324
+ this.fillFromIndex(0, chars);
325
+ this.commitUserValueUpdate();
326
+ const nextEmpty = this.getFirstEmptyIndex();
327
+ this.focusCell(nextEmpty);
328
+ this.emit(Nile_Events.NILE_PASTE, { value: this.value });
329
+ }
330
+ handleCellKeyDown(event) {
331
+ const hasModifier = event.metaKey || event.ctrlKey || event.altKey;
332
+ const target = event.target;
333
+ const index = Number(target.dataset.index ?? 0);
334
+ if (event.key === KeyCode.ENTER && !hasModifier && !event.shiftKey) {
335
+ setTimeout(() => {
336
+ if (!event.defaultPrevented && !event.isComposing) {
337
+ this.formControlController.submit();
338
+ }
339
+ });
340
+ return;
341
+ }
342
+ if (this.disabled || this.readonly) {
343
+ return;
344
+ }
345
+ const isHandledKey = event.key === KeyCode.BACKSPACE ||
346
+ event.key === KeyCode.DELETE ||
347
+ event.key === KeyCode.ARROW_LEFT ||
348
+ event.key === KeyCode.ARROW_RIGHT ||
349
+ event.key === KeyCode.HOME ||
350
+ event.key === KeyCode.END ||
351
+ event.key === KeyCode.SPACE ||
352
+ (event.key.length === 1 && !hasModifier);
353
+ if (!isHandledKey) {
354
+ return;
355
+ }
356
+ event.preventDefault();
357
+ if (event.key === KeyCode.BACKSPACE) {
358
+ if (this.cells[index]) {
359
+ this.updateCell(index, '');
360
+ this.commitUserValueUpdate();
361
+ if (index > 0) {
362
+ this.focusCell(index - 1);
363
+ }
364
+ return;
365
+ }
366
+ if (index > 0) {
367
+ this.updateCell(index - 1, '');
368
+ this.commitUserValueUpdate();
369
+ this.focusCell(index - 1);
370
+ }
371
+ return;
372
+ }
373
+ if (event.key === KeyCode.DELETE) {
374
+ if (this.cells[index]) {
375
+ this.updateCell(index, '');
376
+ this.commitUserValueUpdate();
377
+ }
378
+ return;
379
+ }
380
+ if (event.key === KeyCode.ARROW_LEFT) {
381
+ if (index > 0) {
382
+ this.focusCell(index - 1);
383
+ }
384
+ return;
385
+ }
386
+ if (event.key === KeyCode.ARROW_RIGHT) {
387
+ const firstEmpty = this.getFirstEmptyIndex();
388
+ if (index < firstEmpty) {
389
+ this.focusCell(index + 1);
390
+ }
391
+ return;
392
+ }
393
+ if (event.key === KeyCode.HOME) {
394
+ this.focusCell(0);
395
+ return;
396
+ }
397
+ if (event.key === KeyCode.END) {
398
+ this.focusCell(this.getFirstEmptyIndex());
399
+ return;
400
+ }
401
+ if (event.key === KeyCode.SPACE) {
402
+ return;
403
+ }
404
+ if (event.key.length === 1 && !hasModifier && this.isAllowedCharacter(event.key)) {
405
+ this.updateCell(index, event.key);
406
+ this.commitUserValueUpdate();
407
+ const nextEmpty = this.getFirstEmptyIndex();
408
+ this.focusCell(nextEmpty);
409
+ }
410
+ }
411
+ handleLengthChange() {
412
+ const normalizedLength = this.getNormalizedLength();
413
+ if (this.length !== normalizedLength) {
414
+ this.length = normalizedLength;
415
+ return;
416
+ }
417
+ const normalizedValue = this.normalizeValue(this.value);
418
+ this.syncCellsFromValue(normalizedValue);
419
+ if (normalizedValue !== this.value) {
420
+ this.value = normalizedValue;
421
+ return;
422
+ }
423
+ this.wasComplete = this.isComplete(normalizedValue);
424
+ this.valueInput.value = normalizedValue;
425
+ this.valueInput.setCustomValidity(this.customValidationMessage);
426
+ this.formControlController.updateValidity();
427
+ }
428
+ handleValueChange() {
429
+ const normalizedValue = this.normalizeValue(this.value);
430
+ if (normalizedValue !== this.value) {
431
+ this.value = normalizedValue;
432
+ return;
433
+ }
434
+ this.syncCellsFromValue(normalizedValue);
435
+ this.wasComplete = this.isComplete(normalizedValue);
436
+ this.valueInput.value = normalizedValue;
437
+ this.valueInput.setCustomValidity(this.customValidationMessage);
438
+ this.formControlController.updateValidity();
439
+ }
440
+ handleDisabledChange() {
441
+ if (this.disabled) {
442
+ this.hasFocus = false;
443
+ this.activeIndex = -1;
444
+ this.formControlController.setValidity(true);
445
+ }
446
+ else {
447
+ this.formControlController.updateValidity();
448
+ }
449
+ }
450
+ handleNumericOnlyChange() {
451
+ const normalizedValue = this.normalizeValue(this.value);
452
+ if (normalizedValue !== this.value) {
453
+ this.value = normalizedValue;
454
+ return;
455
+ }
456
+ this.syncCellsFromValue(normalizedValue);
457
+ this.wasComplete = this.isComplete(normalizedValue);
458
+ this.valueInput.value = normalizedValue;
459
+ this.valueInput.setCustomValidity(this.customValidationMessage);
460
+ this.formControlController.updateValidity();
461
+ }
462
+ handlePatternChange() {
463
+ this.valueInput.setCustomValidity(this.customValidationMessage);
464
+ this.formControlController.updateValidity();
465
+ }
466
+ /** Checks validity without showing browser UI. */
467
+ checkValidity() {
468
+ return this.valueInput.checkValidity();
469
+ }
470
+ /** Returns associated form if one exists. */
471
+ getForm() {
472
+ return this.formControlController.getForm();
473
+ }
474
+ /** Checks validity and shows browser UI when invalid. */
475
+ reportValidity() {
476
+ return this.valueInput.reportValidity();
477
+ }
478
+ /** Sets a custom validation message. Pass empty string to restore validity. */
479
+ setCustomValidity(message) {
480
+ this.customValidationMessage = message;
481
+ if (this.valueInput) {
482
+ this.valueInput.setCustomValidity(message);
483
+ }
484
+ this.formControlController.updateValidity();
485
+ }
486
+ /** Focuses the first empty cell, or the last one when complete. */
487
+ focus(options) {
488
+ this.focusCell(this.getFirstEmptyIndex(), options);
489
+ }
490
+ /** Removes focus from whichever OTP cell is currently focused. */
491
+ blur() {
492
+ const active = this.shadowRoot?.activeElement;
493
+ if (active instanceof HTMLElement) {
494
+ active.blur();
495
+ }
496
+ }
497
+ /** Clears all OTP cells. */
498
+ clear() {
499
+ if (this.disabled || this.readonly) {
500
+ return;
501
+ }
502
+ this.cells = Array.from({ length: this.getNormalizedLength() }, () => '');
503
+ this.commitUserValueUpdate();
504
+ this.focusCell(0);
505
+ }
506
+ render() {
507
+ const normalizedLength = this.getNormalizedLength();
508
+ const separatorIndices = this.getSeparatorIndices();
509
+ const hasLabelSlot = this.hasSlotController.test('label');
510
+ const hasHelpTextSlot = this.hasSlotController.test('help-text');
511
+ const hasLabel = Boolean(this.label || hasLabelSlot);
512
+ const hasHelpText = Boolean(this.helpText || hasHelpTextSlot);
513
+ const hasErrorMessage = Boolean(this.errorMessage);
514
+ const describedBy = [
515
+ hasHelpText ? 'help-text' : '',
516
+ hasErrorMessage ? 'error-message' : '',
517
+ ]
518
+ .filter(Boolean)
519
+ .join(' ');
520
+ return html `
521
+ <div
522
+ part="form-control"
523
+ class=${classMap({
524
+ 'form-control': true,
525
+ 'form-control--has-label': hasLabel,
526
+ })}
527
+ >
528
+ <label
529
+ id="label"
530
+ part="form-control-label"
531
+ class="form-control__label"
532
+ aria-hidden=${hasLabel ? 'false' : 'true'}
533
+ >
534
+ <slot name="label">${this.label}</slot>
535
+ </label>
536
+
537
+ <div part="form-control-input" class="form-control-input">
538
+ <div
539
+ part="base"
540
+ class=${classMap({
541
+ otp: true,
542
+ 'otp--warning': this.warning,
543
+ 'otp--error': this.error,
544
+ 'otp--success': this.success,
545
+ 'otp--disabled': this.disabled,
546
+ 'otp--readonly': this.readonly,
547
+ })}
548
+ role="group"
549
+ aria-labelledby=${ifDefined(hasLabel ? 'label' : undefined)}
550
+ aria-label=${ifDefined(hasLabel ? undefined : 'One-time password')}
551
+ aria-describedby=${ifDefined(describedBy || undefined)}
552
+ aria-disabled=${this.disabled ? 'true' : 'false'}
553
+ >
554
+ ${Array.from({ length: normalizedLength }, (_, index) => {
555
+ const value = this.cells[index] ?? '';
556
+ return html `
557
+ <input
558
+ part="cell"
559
+ class=${classMap({
560
+ otp__cell: true,
561
+ 'otp__cell--active': this.activeIndex === index,
562
+ })}
563
+ data-index=${index}
564
+ type=${this.masked && value && this.activeIndex !== index ? OtpInputType.PASSWORD : OtpInputType.TEXT}
565
+ maxlength="1"
566
+ .value=${live(value)}
567
+ ?disabled=${this.disabled}
568
+ ?readonly=${this.readonly}
569
+ placeholder=${ifDefined(this.getCellPlaceholder(index))}
570
+ inputmode=${ifDefined(this.getResolvedInputMode())}
571
+ pattern=${this.isNumericMode() ? OtpCellPattern.NUMERIC : OtpCellPattern.ALPHANUMERIC}
572
+ autocapitalize="none"
573
+ autocorrect="off"
574
+ spellcheck="false"
575
+ autocomplete=${index === 0 ? this.autocomplete : OtpAutoComplete.OFF}
576
+ enterkeyhint=${index === normalizedLength - 1
577
+ ? OtpEnterKeyHint.DONE
578
+ : OtpEnterKeyHint.NEXT}
579
+ aria-label=${`Digit ${index + 1} of ${normalizedLength}`}
580
+ aria-describedby=${ifDefined(describedBy || undefined)}
581
+ aria-invalid=${this.hasAttribute('data-user-invalid')
582
+ ? 'true'
583
+ : 'false'}
584
+ @focus=${this.handleCellFocus}
585
+ @blur=${this.handleCellBlur}
586
+ @keydown=${this.handleCellKeyDown}
587
+ @input=${this.handleCellInput}
588
+ @paste=${this.handleCellPaste}
589
+ />
590
+ ${this.separator && separatorIndices.has(index)
591
+ ? html `
592
+ <span
593
+ class="otp__separator"
594
+ part="separator"
595
+ aria-hidden="true"
596
+ >
597
+ ${this.separator}
598
+ </span>
599
+ `
600
+ : ''}
601
+ `;
602
+ })}
603
+
604
+ <input
605
+ class="otp__value-input"
606
+ type="text"
607
+ .value=${live(this.value)}
608
+ ?required=${this.required}
609
+ ?disabled=${this.disabled}
610
+ minlength=${normalizedLength}
611
+ maxlength=${normalizedLength}
612
+ pattern=${this.getValidationPattern()}
613
+ tabindex="-1"
614
+ aria-hidden="true"
615
+ @focus=${() => this.focus()}
616
+ @invalid=${this.handleInvalid}
617
+ />
618
+ </div>
619
+ </div>
620
+
621
+ ${hasHelpText
622
+ ? html `
623
+ <div
624
+ id="help-text"
625
+ part="form-control-help-text"
626
+ class="form-control__help-text"
627
+ >
628
+ <nile-form-help-text>
629
+ <slot name="help-text">${this.helpText}</slot>
630
+ </nile-form-help-text>
631
+ </div>
632
+ `
633
+ : ``}
634
+ ${hasErrorMessage
635
+ ? html `
636
+ <div
637
+ id="error-message"
638
+ part="form-control-error-message"
639
+ class="form-control__error-message"
640
+ >
641
+ <nile-form-error-message
642
+ >${this.errorMessage}</nile-form-error-message
643
+ >
644
+ </div>
645
+ `
646
+ : ``}
647
+ </div>
648
+ `;
649
+ }
650
+ };
651
+ NileOtpInput.styles = styles;
652
+ __decorate([
653
+ query('.otp__value-input')
654
+ ], NileOtpInput.prototype, "valueInput", void 0);
655
+ __decorate([
656
+ queryAll('.otp__cell')
657
+ ], NileOtpInput.prototype, "cellInputs", void 0);
658
+ __decorate([
659
+ state()
660
+ ], NileOtpInput.prototype, "hasFocus", void 0);
661
+ __decorate([
662
+ state()
663
+ ], NileOtpInput.prototype, "activeIndex", void 0);
664
+ __decorate([
665
+ state()
666
+ ], NileOtpInput.prototype, "cells", void 0);
667
+ __decorate([
668
+ property({ reflect: true, type: String, attribute: true })
669
+ ], NileOtpInput.prototype, "name", void 0);
670
+ __decorate([
671
+ property({ reflect: true, type: String, attribute: true })
672
+ ], NileOtpInput.prototype, "value", void 0);
673
+ __decorate([
674
+ defaultValue()
675
+ ], NileOtpInput.prototype, "defaultValue", void 0);
676
+ __decorate([
677
+ property({ type: Number, reflect: true, attribute: true })
678
+ ], NileOtpInput.prototype, "length", void 0);
679
+ __decorate([
680
+ property({ type: Boolean, reflect: true, attribute: true })
681
+ ], NileOtpInput.prototype, "numericOnly", void 0);
682
+ __decorate([
683
+ property({ type: Boolean, reflect: true, attribute: true })
684
+ ], NileOtpInput.prototype, "alphanumeric", void 0);
685
+ __decorate([
686
+ property({ reflect: true, attribute: true, type: String })
687
+ ], NileOtpInput.prototype, "label", void 0);
688
+ __decorate([
689
+ property({ attribute: true, reflect: true, type: String })
690
+ ], NileOtpInput.prototype, "helpText", void 0);
691
+ __decorate([
692
+ property({ attribute: true, reflect: true, type: String })
693
+ ], NileOtpInput.prototype, "errorMessage", void 0);
694
+ __decorate([
695
+ property({ reflect: true, attribute: true, type: String })
696
+ ], NileOtpInput.prototype, "placeholder", void 0);
697
+ __decorate([
698
+ property({ reflect: true, type: String })
699
+ ], NileOtpInput.prototype, "separator", void 0);
700
+ __decorate([
701
+ property({ type: Number, attribute: true, reflect: true })
702
+ ], NileOtpInput.prototype, "separatorEvery", void 0);
703
+ __decorate([
704
+ property({ attribute: 'separator-positions', type: String, reflect: true })
705
+ ], NileOtpInput.prototype, "separatorPositions", void 0);
706
+ __decorate([
707
+ property({ type: Boolean, reflect: true })
708
+ ], NileOtpInput.prototype, "masked", void 0);
709
+ __decorate([
710
+ property({ type: Boolean, attribute: true, reflect: true })
711
+ ], NileOtpInput.prototype, "warning", void 0);
712
+ __decorate([
713
+ property({ type: Boolean, attribute: true, reflect: true })
714
+ ], NileOtpInput.prototype, "error", void 0);
715
+ __decorate([
716
+ property({ type: Boolean, attribute: true, reflect: true })
717
+ ], NileOtpInput.prototype, "success", void 0);
718
+ __decorate([
719
+ property({ type: Boolean, reflect: true, attribute: true })
720
+ ], NileOtpInput.prototype, "disabled", void 0);
721
+ __decorate([
722
+ property({ type: Boolean, attribute: true, reflect: true })
723
+ ], NileOtpInput.prototype, "readonly", void 0);
724
+ __decorate([
725
+ property({ reflect: true, attribute: true, type: String })
726
+ ], NileOtpInput.prototype, "form", void 0);
727
+ __decorate([
728
+ property({ type: Boolean, reflect: true, attribute: true })
729
+ ], NileOtpInput.prototype, "required", void 0);
730
+ __decorate([
731
+ property({ reflect: true, attribute: true, type: String })
732
+ ], NileOtpInput.prototype, "pattern", void 0);
733
+ __decorate([
734
+ property({ type: Boolean, reflect: true, attribute: true })
735
+ ], NileOtpInput.prototype, "autofocus", void 0);
736
+ __decorate([
737
+ property({ reflect: true, attribute: true, type: String })
738
+ ], NileOtpInput.prototype, "inputmode", void 0);
739
+ __decorate([
740
+ property({ reflect: true, type: String })
741
+ ], NileOtpInput.prototype, "autocomplete", void 0);
742
+ __decorate([
743
+ watch('length', { waitUntilFirstUpdate: true })
744
+ ], NileOtpInput.prototype, "handleLengthChange", null);
745
+ __decorate([
746
+ watch('value', { waitUntilFirstUpdate: true })
747
+ ], NileOtpInput.prototype, "handleValueChange", null);
748
+ __decorate([
749
+ watch('disabled', { waitUntilFirstUpdate: true })
750
+ ], NileOtpInput.prototype, "handleDisabledChange", null);
751
+ __decorate([
752
+ watch('numericOnly', { waitUntilFirstUpdate: true })
753
+ ], NileOtpInput.prototype, "handleNumericOnlyChange", null);
754
+ __decorate([
755
+ watch('pattern', { waitUntilFirstUpdate: true })
756
+ ], NileOtpInput.prototype, "handlePatternChange", null);
757
+ NileOtpInput = __decorate([
758
+ customElement('nile-otp-input')
759
+ ], NileOtpInput);
760
+ export { NileOtpInput };
761
+ export default NileOtpInput;
762
+ //# sourceMappingURL=nile-otp-input.js.map