@ionic/core 8.6.5-dev.11752242329.17d249a3 → 8.6.5-dev.11752245279.14b7cb03
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/components/ion-input-otp.js +104 -64
- package/components/ion-input.js +2 -2
- package/components/modal.js +4 -36
- package/dist/cjs/ion-input-otp.cjs.entry.js +103 -64
- package/dist/cjs/ion-input.cjs.entry.js +2 -2
- package/dist/cjs/ion-modal.cjs.entry.js +4 -36
- package/dist/cjs/ionic.cjs.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/input/input.ios.css +1 -1
- package/dist/collection/components/input/input.md.css +1 -1
- package/dist/collection/components/input-otp/input-otp.js +105 -65
- package/dist/collection/components/modal/modal.js +4 -36
- package/dist/docs.json +1 -1
- package/dist/esm/ion-input-otp.entry.js +103 -64
- package/dist/esm/ion-input.entry.js +2 -2
- package/dist/esm/ion-modal.entry.js +4 -36
- package/dist/esm/ionic.js +1 -1
- package/dist/esm/loader.js +1 -1
- package/dist/ionic/ionic.esm.js +1 -1
- package/dist/ionic/p-56712fd4.entry.js +4 -0
- package/dist/ionic/p-5c88e4c6.entry.js +4 -0
- package/dist/ionic/p-a75de5e8.entry.js +4 -0
- package/dist/types/components/input-otp/input-otp.d.ts +12 -8
- package/dist/types/components/modal/modal.d.ts +0 -9
- package/hydrate/index.js +110 -102
- package/hydrate/index.mjs +110 -102
- package/package.json +1 -1
- package/dist/ionic/p-29f784e8.entry.js +0 -4
- package/dist/ionic/p-2bc871ee.entry.js +0 -4
- package/dist/ionic/p-e30ff968.entry.js +0 -4
|
@@ -21,6 +21,7 @@ export class InputOTP {
|
|
|
21
21
|
this.isKeyboardNavigation = false;
|
|
22
22
|
this.inputValues = [];
|
|
23
23
|
this.hasFocus = false;
|
|
24
|
+
this.previousInputValues = [];
|
|
24
25
|
/**
|
|
25
26
|
* Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
|
|
26
27
|
* Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
|
|
@@ -121,19 +122,12 @@ export class InputOTP {
|
|
|
121
122
|
}
|
|
122
123
|
};
|
|
123
124
|
/**
|
|
124
|
-
* Handles keyboard navigation
|
|
125
|
+
* Handles keyboard navigation for the OTP component.
|
|
125
126
|
*
|
|
126
127
|
* Navigation:
|
|
127
128
|
* - Backspace: Clears current input and moves to previous box if empty
|
|
128
129
|
* - Arrow Left/Right: Moves focus between input boxes
|
|
129
130
|
* - Tab: Allows normal tab navigation between components
|
|
130
|
-
*
|
|
131
|
-
* Input Behavior:
|
|
132
|
-
* - Validates input against the allowed pattern
|
|
133
|
-
* - When entering a key in a filled box:
|
|
134
|
-
* - Shifts existing values right if there is room
|
|
135
|
-
* - Updates the value of the input group
|
|
136
|
-
* - Prevents default behavior to avoid automatic focus shift
|
|
137
131
|
*/
|
|
138
132
|
this.onKeyDown = (index) => (event) => {
|
|
139
133
|
const { length } = this;
|
|
@@ -188,69 +182,118 @@ export class InputOTP {
|
|
|
188
182
|
// Let all tab events proceed normally
|
|
189
183
|
return;
|
|
190
184
|
}
|
|
191
|
-
// If the input box contains a value and the key being
|
|
192
|
-
// entered is a valid key for the input box update the value
|
|
193
|
-
// and shift the values to the right if there is room.
|
|
194
|
-
if (this.inputValues[index] && this.validKeyPattern.test(event.key)) {
|
|
195
|
-
if (!this.inputValues[length - 1]) {
|
|
196
|
-
for (let i = length - 1; i > index; i--) {
|
|
197
|
-
this.inputValues[i] = this.inputValues[i - 1];
|
|
198
|
-
this.inputRefs[i].value = this.inputValues[i] || '';
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
this.inputValues[index] = event.key;
|
|
202
|
-
this.inputRefs[index].value = event.key;
|
|
203
|
-
this.updateValue(event);
|
|
204
|
-
// Prevent default to avoid the browser from
|
|
205
|
-
// automatically moving the focus to the next input
|
|
206
|
-
event.preventDefault();
|
|
207
|
-
}
|
|
208
185
|
};
|
|
186
|
+
/**
|
|
187
|
+
* Processes all input scenarios for each input box.
|
|
188
|
+
*
|
|
189
|
+
* This function manages:
|
|
190
|
+
* 1. Autofill handling
|
|
191
|
+
* 2. Input validation
|
|
192
|
+
* 3. Full selection replacement or typing in an empty box
|
|
193
|
+
* 4. Inserting in the middle with available space (shifting)
|
|
194
|
+
* 5. Single character replacement
|
|
195
|
+
*/
|
|
209
196
|
this.onInput = (index) => (event) => {
|
|
197
|
+
var _a, _b;
|
|
210
198
|
const { length, validKeyPattern } = this;
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
199
|
+
const input = event.target;
|
|
200
|
+
const value = input.value;
|
|
201
|
+
const previousValue = this.previousInputValues[index] || '';
|
|
202
|
+
// 1. Autofill handling
|
|
203
|
+
// If the length of the value increases by more than 1 from the previous
|
|
204
|
+
// value, treat this as autofill. This is to prevent the case where the
|
|
205
|
+
// user is typing a single character into an input box containing a value
|
|
206
|
+
// as that will trigger this function with a value length of 2 characters.
|
|
207
|
+
const isAutofill = value.length - previousValue.length > 1;
|
|
208
|
+
if (isAutofill) {
|
|
209
|
+
// Distribute valid characters across input boxes
|
|
215
210
|
const validChars = value
|
|
216
211
|
.split('')
|
|
217
212
|
.filter((char) => validKeyPattern.test(char))
|
|
218
213
|
.slice(0, length);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (validChars.length === 0) {
|
|
223
|
-
requestAnimationFrame(() => {
|
|
224
|
-
this.inputRefs.forEach((input) => {
|
|
225
|
-
input.value = '';
|
|
226
|
-
});
|
|
227
|
-
});
|
|
214
|
+
for (let i = 0; i < length; i++) {
|
|
215
|
+
this.inputValues[i] = validChars[i] || '';
|
|
216
|
+
this.inputRefs[i].value = validChars[i] || '';
|
|
228
217
|
}
|
|
229
|
-
// Update the value of the input group and emit the input change event
|
|
230
|
-
this.value = validChars.join('');
|
|
231
218
|
this.updateValue(event);
|
|
232
|
-
// Focus the
|
|
233
|
-
// are filled after a small delay to ensure the input boxes have been
|
|
234
|
-
// updated before moving the focus
|
|
219
|
+
// Focus the next empty input or the last one
|
|
235
220
|
setTimeout(() => {
|
|
236
|
-
var _a;
|
|
237
221
|
const nextIndex = validChars.length < length ? validChars.length : length - 1;
|
|
238
|
-
|
|
222
|
+
this.inputRefs[nextIndex].focus();
|
|
239
223
|
}, 20);
|
|
224
|
+
this.previousInputValues = [...this.inputValues];
|
|
240
225
|
return;
|
|
241
226
|
}
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
227
|
+
// 2. Input validation
|
|
228
|
+
// If the character entered is invalid (does not match the pattern),
|
|
229
|
+
// restore the previous value and exit
|
|
230
|
+
if (value.length > 0 && !validKeyPattern.test(value[value.length - 1])) {
|
|
231
|
+
input.value = this.inputValues[index] || '';
|
|
232
|
+
this.previousInputValues = [...this.inputValues];
|
|
246
233
|
return;
|
|
247
234
|
}
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
this.
|
|
251
|
-
|
|
235
|
+
// 3. Full selection replacement or typing in an empty box
|
|
236
|
+
// If the user selects all text in the input box and types, or if the
|
|
237
|
+
// input box is empty, replace only this input box. If the box is empty,
|
|
238
|
+
// move to the next box, otherwise stay focused on this box.
|
|
239
|
+
const isAllSelected = input.selectionStart === 0 && input.selectionEnd === value.length;
|
|
240
|
+
const isEmpty = !this.inputValues[index];
|
|
241
|
+
if (isAllSelected || isEmpty) {
|
|
242
|
+
this.inputValues[index] = value;
|
|
243
|
+
input.value = value;
|
|
244
|
+
this.updateValue(event);
|
|
252
245
|
this.focusNext(index);
|
|
246
|
+
this.previousInputValues = [...this.inputValues];
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// 4. Inserting in the middle with available space (shifting)
|
|
250
|
+
// If typing in a filled input box and there are empty boxes at the end,
|
|
251
|
+
// shift all values starting at the current box to the right, and insert
|
|
252
|
+
// the new character at the current box.
|
|
253
|
+
const hasAvailableBoxAtEnd = this.inputValues[this.inputValues.length - 1] === '';
|
|
254
|
+
if (this.inputValues[index] && hasAvailableBoxAtEnd && value.length === 2) {
|
|
255
|
+
// Get the inserted character (from event or by diffing value/previousValue)
|
|
256
|
+
let newChar = event.data;
|
|
257
|
+
if (!newChar) {
|
|
258
|
+
newChar = value.split('').find((c, i) => c !== previousValue[i]) || value[value.length - 1];
|
|
259
|
+
}
|
|
260
|
+
// Validate the new character before shifting
|
|
261
|
+
if (!validKeyPattern.test(newChar)) {
|
|
262
|
+
input.value = this.inputValues[index] || '';
|
|
263
|
+
this.previousInputValues = [...this.inputValues];
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
// Shift values right from the end to the insertion point
|
|
267
|
+
for (let i = this.inputValues.length - 1; i > index; i--) {
|
|
268
|
+
this.inputValues[i] = this.inputValues[i - 1];
|
|
269
|
+
this.inputRefs[i].value = this.inputValues[i] || '';
|
|
270
|
+
}
|
|
271
|
+
this.inputValues[index] = newChar;
|
|
272
|
+
this.inputRefs[index].value = newChar;
|
|
273
|
+
this.updateValue(event);
|
|
274
|
+
this.previousInputValues = [...this.inputValues];
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// 5. Single character replacement
|
|
278
|
+
// Handles replacing a single character in a box containing a value based
|
|
279
|
+
// on the cursor position. We need the cursor position to determine which
|
|
280
|
+
// character was the last character typed. For example, if the user types "2"
|
|
281
|
+
// in an input box with the cursor at the beginning of the value of "6",
|
|
282
|
+
// the value will be "26", but we want to grab the "2" as the last character
|
|
283
|
+
// typed.
|
|
284
|
+
const cursorPos = (_a = input.selectionStart) !== null && _a !== void 0 ? _a : value.length;
|
|
285
|
+
const newCharIndex = cursorPos - 1;
|
|
286
|
+
const newChar = (_b = value[newCharIndex]) !== null && _b !== void 0 ? _b : value[0];
|
|
287
|
+
// Check if the new character is valid before updating the value
|
|
288
|
+
if (!validKeyPattern.test(newChar)) {
|
|
289
|
+
input.value = this.inputValues[index] || '';
|
|
290
|
+
this.previousInputValues = [...this.inputValues];
|
|
291
|
+
return;
|
|
253
292
|
}
|
|
293
|
+
this.inputValues[index] = newChar;
|
|
294
|
+
input.value = newChar;
|
|
295
|
+
this.updateValue(event);
|
|
296
|
+
this.previousInputValues = [...this.inputValues];
|
|
254
297
|
};
|
|
255
298
|
/**
|
|
256
299
|
* Handles pasting text into the input OTP component.
|
|
@@ -260,7 +303,7 @@ export class InputOTP {
|
|
|
260
303
|
* the next empty input after pasting.
|
|
261
304
|
*/
|
|
262
305
|
this.onPaste = (event) => {
|
|
263
|
-
var _a, _b
|
|
306
|
+
var _a, _b;
|
|
264
307
|
const { inputRefs, length, validKeyPattern } = this;
|
|
265
308
|
event.preventDefault();
|
|
266
309
|
const pastedText = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text');
|
|
@@ -287,13 +330,8 @@ export class InputOTP {
|
|
|
287
330
|
this.updateValue(event);
|
|
288
331
|
// Focus the next empty input after pasting
|
|
289
332
|
// If all boxes are filled, focus the last input
|
|
290
|
-
const nextEmptyIndex = validChars.length;
|
|
291
|
-
|
|
292
|
-
(_b = inputRefs[nextEmptyIndex]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
(_c = inputRefs[length - 1]) === null || _c === void 0 ? void 0 : _c.focus();
|
|
296
|
-
}
|
|
333
|
+
const nextEmptyIndex = validChars.length < length ? validChars.length : length - 1;
|
|
334
|
+
(_b = inputRefs[nextEmptyIndex]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
297
335
|
};
|
|
298
336
|
}
|
|
299
337
|
/**
|
|
@@ -432,6 +470,7 @@ export class InputOTP {
|
|
|
432
470
|
});
|
|
433
471
|
// Update the value without emitting events
|
|
434
472
|
this.value = this.inputValues.join('');
|
|
473
|
+
this.previousInputValues = [...this.inputValues];
|
|
435
474
|
}
|
|
436
475
|
/**
|
|
437
476
|
* Updates the value of the input group.
|
|
@@ -553,7 +592,7 @@ export class InputOTP {
|
|
|
553
592
|
const tabbableIndex = this.getTabbableIndex();
|
|
554
593
|
const pattern = this.getPattern();
|
|
555
594
|
const hasDescription = ((_b = (_a = el.querySelector('.input-otp-description')) === null || _a === void 0 ? void 0 : _a.textContent) === null || _b === void 0 ? void 0 : _b.trim()) !== '';
|
|
556
|
-
return (h(Host, { key: '
|
|
595
|
+
return (h(Host, { key: '37d7d3663a6d2450862ba2e14796460dbb89db20', class: createColorClasses(color, {
|
|
557
596
|
[mode]: true,
|
|
558
597
|
'has-focus': hasFocus,
|
|
559
598
|
[`input-otp-size-${size}`]: true,
|
|
@@ -561,10 +600,10 @@ export class InputOTP {
|
|
|
561
600
|
[`input-otp-fill-${fill}`]: true,
|
|
562
601
|
'input-otp-disabled': disabled,
|
|
563
602
|
'input-otp-readonly': readonly,
|
|
564
|
-
}) }, h("div", Object.assign({ key: '
|
|
603
|
+
}) }, h("div", Object.assign({ key: '1cf5d9e680229adc0f918d84143dbcd557bb2ab2', role: "group", "aria-label": "One-time password input", class: "input-otp-group" }, inheritedAttributes), Array.from({ length }).map((_, index) => (h(Fragment, null, h("div", { class: "native-wrapper" }, h("input", { class: "native-input", id: `${inputId}-${index}`, "aria-label": `Input ${index + 1} of ${length}`, type: "text", autoCapitalize: autocapitalize, inputmode: inputmode, pattern: pattern, disabled: disabled, readOnly: readonly, tabIndex: index === tabbableIndex ? 0 : -1, value: inputValues[index] || '', autocomplete: "one-time-code", ref: (el) => (inputRefs[index] = el), onInput: this.onInput(index), onBlur: this.onBlur, onFocus: this.onFocus(index), onKeyDown: this.onKeyDown(index), onPaste: this.onPaste })), this.showSeparator(index) && h("div", { class: "input-otp-separator" }))))), h("div", { key: '646ad36d4d49339c502ebca8c7bd81763ff54119', class: {
|
|
565
604
|
'input-otp-description': true,
|
|
566
605
|
'input-otp-description-hidden': !hasDescription,
|
|
567
|
-
} }, h("slot", { key: '
|
|
606
|
+
} }, h("slot", { key: 'b3594ae5455ec09868da0a7b76835157e921949a' }))));
|
|
568
607
|
}
|
|
569
608
|
static get is() { return "ion-input-otp"; }
|
|
570
609
|
static get encapsulation() { return "scoped"; }
|
|
@@ -849,7 +888,8 @@ export class InputOTP {
|
|
|
849
888
|
static get states() {
|
|
850
889
|
return {
|
|
851
890
|
"inputValues": {},
|
|
852
|
-
"hasFocus": {}
|
|
891
|
+
"hasFocus": {},
|
|
892
|
+
"previousInputValues": {}
|
|
853
893
|
};
|
|
854
894
|
}
|
|
855
895
|
static get events() {
|
|
@@ -196,26 +196,6 @@ export class Modal {
|
|
|
196
196
|
dragHandleEl.focus();
|
|
197
197
|
}
|
|
198
198
|
};
|
|
199
|
-
/**
|
|
200
|
-
* When the slot changes, we need to find all the modals in the slot
|
|
201
|
-
* and set the data-parent-ion-modal attribute on them so we can find them
|
|
202
|
-
* and dismiss them when we get dismissed.
|
|
203
|
-
* We need to do it this way because when a modal is opened, it's moved to
|
|
204
|
-
* the end of the body and is no longer an actual child of the modal.
|
|
205
|
-
*/
|
|
206
|
-
this.onSlotChange = ({ target }) => {
|
|
207
|
-
const slot = target;
|
|
208
|
-
slot.assignedElements().forEach((el) => {
|
|
209
|
-
el.querySelectorAll('ion-modal').forEach((childModal) => {
|
|
210
|
-
// We don't need to write to the DOM if the modal is already tagged
|
|
211
|
-
// If this is a deeply nested modal, this effect should cascade so we don't
|
|
212
|
-
// need to worry about another modal claiming the same child.
|
|
213
|
-
if (childModal.getAttribute('data-parent-ion-modal') === null) {
|
|
214
|
-
childModal.setAttribute('data-parent-ion-modal', this.el.id);
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
};
|
|
219
199
|
}
|
|
220
200
|
onIsOpenChange(newValue, oldValue) {
|
|
221
201
|
if (newValue === true && oldValue === false) {
|
|
@@ -579,12 +559,6 @@ export class Modal {
|
|
|
579
559
|
* in case the dismiss transition does run.
|
|
580
560
|
*/
|
|
581
561
|
const unlock = await this.lockController.lock();
|
|
582
|
-
/**
|
|
583
|
-
* Dismiss all child modals. This is especially important in
|
|
584
|
-
* Angular and React because it's possible to lose control of a child
|
|
585
|
-
* modal when the parent modal is dismissed.
|
|
586
|
-
*/
|
|
587
|
-
await this.dismissNestedModals();
|
|
588
562
|
/**
|
|
589
563
|
* If a canDismiss handler is responsible
|
|
590
564
|
* for calling the dismiss method, we should
|
|
@@ -811,12 +785,6 @@ export class Modal {
|
|
|
811
785
|
}
|
|
812
786
|
}
|
|
813
787
|
}
|
|
814
|
-
async dismissNestedModals() {
|
|
815
|
-
const nestedModals = document.querySelectorAll(`ion-modal[data-parent-ion-modal="${this.el.id}"]`);
|
|
816
|
-
nestedModals === null || nestedModals === void 0 ? void 0 : nestedModals.forEach(async (modal) => {
|
|
817
|
-
await modal.dismiss(undefined, 'parent-dismissed');
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
788
|
render() {
|
|
821
789
|
const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
|
|
822
790
|
const showHandle = handle !== false && isSheetModal;
|
|
@@ -824,20 +792,20 @@ export class Modal {
|
|
|
824
792
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
825
793
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
826
794
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
827
|
-
return (h(Host, Object.assign({ key: '
|
|
795
|
+
return (h(Host, Object.assign({ key: '1980fa23331381c568a2be8091d888e09754fc52', "no-router": true,
|
|
828
796
|
// Allow the modal to be navigable when the handle is focusable
|
|
829
797
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
830
798
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
831
|
-
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: '
|
|
799
|
+
}, class: Object.assign({ [mode]: true, ['modal-default']: !isCardModal && !isSheetModal, [`modal-card`]: isCardModal, [`modal-sheet`]: isSheetModal, [`modal-no-expand-scroll`]: isSheetModal && !expandToScroll, 'overlay-hidden': true, [FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false }, getClassMap(this.cssClass)), onIonBackdropTap: this.onBackdropTap, onIonModalDidPresent: this.onLifecycle, onIonModalWillPresent: this.onLifecycle, onIonModalWillDismiss: this.onLifecycle, onIonModalDidDismiss: this.onLifecycle, onFocus: this.onModalFocus }), h("ion-backdrop", { key: 'ba94b055c064e2907eabbe6d7a43cb52adff1048', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '991f47859250d2143275ebb9b0b01a6ea8c491c0', class: "modal-shadow" }), h("div", Object.assign({ key: '02ecf8ac6a5bdb309ff993cc74a3911e99502a89',
|
|
832
800
|
/*
|
|
833
801
|
role and aria-modal must be used on the
|
|
834
802
|
same element. They must also be set inside the
|
|
835
803
|
shadow DOM otherwise ion-button will not be highlighted
|
|
836
804
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
837
805
|
*/
|
|
838
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '
|
|
806
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '0180a4d6952e41bfd736272d1a49d47d86ca7fef', class: "modal-handle",
|
|
839
807
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
840
|
-
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: '
|
|
808
|
+
tabIndex: !isHandleCycle ? -1 : 0, "aria-label": "Activate to adjust the size of the dialog overlaying the screen", onClick: isHandleCycle ? this.onHandleClick : undefined, part: "handle", ref: (el) => (this.dragHandleEl = el) })), h("slot", { key: 'd062f330675f730ad70c23267baed200ca9b43b0' }))));
|
|
841
809
|
}
|
|
842
810
|
static get is() { return "ion-modal"; }
|
|
843
811
|
static get encapsulation() { return "shadow"; }
|
package/dist/docs.json
CHANGED
|
@@ -30,6 +30,7 @@ const InputOTP = class {
|
|
|
30
30
|
this.isKeyboardNavigation = false;
|
|
31
31
|
this.inputValues = [];
|
|
32
32
|
this.hasFocus = false;
|
|
33
|
+
this.previousInputValues = [];
|
|
33
34
|
/**
|
|
34
35
|
* Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
|
|
35
36
|
* Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
|
|
@@ -130,19 +131,12 @@ const InputOTP = class {
|
|
|
130
131
|
}
|
|
131
132
|
};
|
|
132
133
|
/**
|
|
133
|
-
* Handles keyboard navigation
|
|
134
|
+
* Handles keyboard navigation for the OTP component.
|
|
134
135
|
*
|
|
135
136
|
* Navigation:
|
|
136
137
|
* - Backspace: Clears current input and moves to previous box if empty
|
|
137
138
|
* - Arrow Left/Right: Moves focus between input boxes
|
|
138
139
|
* - Tab: Allows normal tab navigation between components
|
|
139
|
-
*
|
|
140
|
-
* Input Behavior:
|
|
141
|
-
* - Validates input against the allowed pattern
|
|
142
|
-
* - When entering a key in a filled box:
|
|
143
|
-
* - Shifts existing values right if there is room
|
|
144
|
-
* - Updates the value of the input group
|
|
145
|
-
* - Prevents default behavior to avoid automatic focus shift
|
|
146
140
|
*/
|
|
147
141
|
this.onKeyDown = (index) => (event) => {
|
|
148
142
|
const { length } = this;
|
|
@@ -197,69 +191,118 @@ const InputOTP = class {
|
|
|
197
191
|
// Let all tab events proceed normally
|
|
198
192
|
return;
|
|
199
193
|
}
|
|
200
|
-
// If the input box contains a value and the key being
|
|
201
|
-
// entered is a valid key for the input box update the value
|
|
202
|
-
// and shift the values to the right if there is room.
|
|
203
|
-
if (this.inputValues[index] && this.validKeyPattern.test(event.key)) {
|
|
204
|
-
if (!this.inputValues[length - 1]) {
|
|
205
|
-
for (let i = length - 1; i > index; i--) {
|
|
206
|
-
this.inputValues[i] = this.inputValues[i - 1];
|
|
207
|
-
this.inputRefs[i].value = this.inputValues[i] || '';
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
this.inputValues[index] = event.key;
|
|
211
|
-
this.inputRefs[index].value = event.key;
|
|
212
|
-
this.updateValue(event);
|
|
213
|
-
// Prevent default to avoid the browser from
|
|
214
|
-
// automatically moving the focus to the next input
|
|
215
|
-
event.preventDefault();
|
|
216
|
-
}
|
|
217
194
|
};
|
|
195
|
+
/**
|
|
196
|
+
* Processes all input scenarios for each input box.
|
|
197
|
+
*
|
|
198
|
+
* This function manages:
|
|
199
|
+
* 1. Autofill handling
|
|
200
|
+
* 2. Input validation
|
|
201
|
+
* 3. Full selection replacement or typing in an empty box
|
|
202
|
+
* 4. Inserting in the middle with available space (shifting)
|
|
203
|
+
* 5. Single character replacement
|
|
204
|
+
*/
|
|
218
205
|
this.onInput = (index) => (event) => {
|
|
206
|
+
var _a, _b;
|
|
219
207
|
const { length, validKeyPattern } = this;
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
208
|
+
const input = event.target;
|
|
209
|
+
const value = input.value;
|
|
210
|
+
const previousValue = this.previousInputValues[index] || '';
|
|
211
|
+
// 1. Autofill handling
|
|
212
|
+
// If the length of the value increases by more than 1 from the previous
|
|
213
|
+
// value, treat this as autofill. This is to prevent the case where the
|
|
214
|
+
// user is typing a single character into an input box containing a value
|
|
215
|
+
// as that will trigger this function with a value length of 2 characters.
|
|
216
|
+
const isAutofill = value.length - previousValue.length > 1;
|
|
217
|
+
if (isAutofill) {
|
|
218
|
+
// Distribute valid characters across input boxes
|
|
224
219
|
const validChars = value
|
|
225
220
|
.split('')
|
|
226
221
|
.filter((char) => validKeyPattern.test(char))
|
|
227
222
|
.slice(0, length);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (validChars.length === 0) {
|
|
232
|
-
requestAnimationFrame(() => {
|
|
233
|
-
this.inputRefs.forEach((input) => {
|
|
234
|
-
input.value = '';
|
|
235
|
-
});
|
|
236
|
-
});
|
|
223
|
+
for (let i = 0; i < length; i++) {
|
|
224
|
+
this.inputValues[i] = validChars[i] || '';
|
|
225
|
+
this.inputRefs[i].value = validChars[i] || '';
|
|
237
226
|
}
|
|
238
|
-
// Update the value of the input group and emit the input change event
|
|
239
|
-
this.value = validChars.join('');
|
|
240
227
|
this.updateValue(event);
|
|
241
|
-
// Focus the
|
|
242
|
-
// are filled after a small delay to ensure the input boxes have been
|
|
243
|
-
// updated before moving the focus
|
|
228
|
+
// Focus the next empty input or the last one
|
|
244
229
|
setTimeout(() => {
|
|
245
|
-
var _a;
|
|
246
230
|
const nextIndex = validChars.length < length ? validChars.length : length - 1;
|
|
247
|
-
|
|
231
|
+
this.inputRefs[nextIndex].focus();
|
|
248
232
|
}, 20);
|
|
233
|
+
this.previousInputValues = [...this.inputValues];
|
|
249
234
|
return;
|
|
250
235
|
}
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
236
|
+
// 2. Input validation
|
|
237
|
+
// If the character entered is invalid (does not match the pattern),
|
|
238
|
+
// restore the previous value and exit
|
|
239
|
+
if (value.length > 0 && !validKeyPattern.test(value[value.length - 1])) {
|
|
240
|
+
input.value = this.inputValues[index] || '';
|
|
241
|
+
this.previousInputValues = [...this.inputValues];
|
|
255
242
|
return;
|
|
256
243
|
}
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
this.
|
|
260
|
-
|
|
244
|
+
// 3. Full selection replacement or typing in an empty box
|
|
245
|
+
// If the user selects all text in the input box and types, or if the
|
|
246
|
+
// input box is empty, replace only this input box. If the box is empty,
|
|
247
|
+
// move to the next box, otherwise stay focused on this box.
|
|
248
|
+
const isAllSelected = input.selectionStart === 0 && input.selectionEnd === value.length;
|
|
249
|
+
const isEmpty = !this.inputValues[index];
|
|
250
|
+
if (isAllSelected || isEmpty) {
|
|
251
|
+
this.inputValues[index] = value;
|
|
252
|
+
input.value = value;
|
|
253
|
+
this.updateValue(event);
|
|
261
254
|
this.focusNext(index);
|
|
255
|
+
this.previousInputValues = [...this.inputValues];
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
// 4. Inserting in the middle with available space (shifting)
|
|
259
|
+
// If typing in a filled input box and there are empty boxes at the end,
|
|
260
|
+
// shift all values starting at the current box to the right, and insert
|
|
261
|
+
// the new character at the current box.
|
|
262
|
+
const hasAvailableBoxAtEnd = this.inputValues[this.inputValues.length - 1] === '';
|
|
263
|
+
if (this.inputValues[index] && hasAvailableBoxAtEnd && value.length === 2) {
|
|
264
|
+
// Get the inserted character (from event or by diffing value/previousValue)
|
|
265
|
+
let newChar = event.data;
|
|
266
|
+
if (!newChar) {
|
|
267
|
+
newChar = value.split('').find((c, i) => c !== previousValue[i]) || value[value.length - 1];
|
|
268
|
+
}
|
|
269
|
+
// Validate the new character before shifting
|
|
270
|
+
if (!validKeyPattern.test(newChar)) {
|
|
271
|
+
input.value = this.inputValues[index] || '';
|
|
272
|
+
this.previousInputValues = [...this.inputValues];
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
// Shift values right from the end to the insertion point
|
|
276
|
+
for (let i = this.inputValues.length - 1; i > index; i--) {
|
|
277
|
+
this.inputValues[i] = this.inputValues[i - 1];
|
|
278
|
+
this.inputRefs[i].value = this.inputValues[i] || '';
|
|
279
|
+
}
|
|
280
|
+
this.inputValues[index] = newChar;
|
|
281
|
+
this.inputRefs[index].value = newChar;
|
|
282
|
+
this.updateValue(event);
|
|
283
|
+
this.previousInputValues = [...this.inputValues];
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
// 5. Single character replacement
|
|
287
|
+
// Handles replacing a single character in a box containing a value based
|
|
288
|
+
// on the cursor position. We need the cursor position to determine which
|
|
289
|
+
// character was the last character typed. For example, if the user types "2"
|
|
290
|
+
// in an input box with the cursor at the beginning of the value of "6",
|
|
291
|
+
// the value will be "26", but we want to grab the "2" as the last character
|
|
292
|
+
// typed.
|
|
293
|
+
const cursorPos = (_a = input.selectionStart) !== null && _a !== void 0 ? _a : value.length;
|
|
294
|
+
const newCharIndex = cursorPos - 1;
|
|
295
|
+
const newChar = (_b = value[newCharIndex]) !== null && _b !== void 0 ? _b : value[0];
|
|
296
|
+
// Check if the new character is valid before updating the value
|
|
297
|
+
if (!validKeyPattern.test(newChar)) {
|
|
298
|
+
input.value = this.inputValues[index] || '';
|
|
299
|
+
this.previousInputValues = [...this.inputValues];
|
|
300
|
+
return;
|
|
262
301
|
}
|
|
302
|
+
this.inputValues[index] = newChar;
|
|
303
|
+
input.value = newChar;
|
|
304
|
+
this.updateValue(event);
|
|
305
|
+
this.previousInputValues = [...this.inputValues];
|
|
263
306
|
};
|
|
264
307
|
/**
|
|
265
308
|
* Handles pasting text into the input OTP component.
|
|
@@ -269,7 +312,7 @@ const InputOTP = class {
|
|
|
269
312
|
* the next empty input after pasting.
|
|
270
313
|
*/
|
|
271
314
|
this.onPaste = (event) => {
|
|
272
|
-
var _a, _b
|
|
315
|
+
var _a, _b;
|
|
273
316
|
const { inputRefs, length, validKeyPattern } = this;
|
|
274
317
|
event.preventDefault();
|
|
275
318
|
const pastedText = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text');
|
|
@@ -296,13 +339,8 @@ const InputOTP = class {
|
|
|
296
339
|
this.updateValue(event);
|
|
297
340
|
// Focus the next empty input after pasting
|
|
298
341
|
// If all boxes are filled, focus the last input
|
|
299
|
-
const nextEmptyIndex = validChars.length;
|
|
300
|
-
|
|
301
|
-
(_b = inputRefs[nextEmptyIndex]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
302
|
-
}
|
|
303
|
-
else {
|
|
304
|
-
(_c = inputRefs[length - 1]) === null || _c === void 0 ? void 0 : _c.focus();
|
|
305
|
-
}
|
|
342
|
+
const nextEmptyIndex = validChars.length < length ? validChars.length : length - 1;
|
|
343
|
+
(_b = inputRefs[nextEmptyIndex]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
306
344
|
};
|
|
307
345
|
}
|
|
308
346
|
/**
|
|
@@ -441,6 +479,7 @@ const InputOTP = class {
|
|
|
441
479
|
});
|
|
442
480
|
// Update the value without emitting events
|
|
443
481
|
this.value = this.inputValues.join('');
|
|
482
|
+
this.previousInputValues = [...this.inputValues];
|
|
444
483
|
}
|
|
445
484
|
/**
|
|
446
485
|
* Updates the value of the input group.
|
|
@@ -562,7 +601,7 @@ const InputOTP = class {
|
|
|
562
601
|
const tabbableIndex = this.getTabbableIndex();
|
|
563
602
|
const pattern = this.getPattern();
|
|
564
603
|
const hasDescription = ((_b = (_a = el.querySelector('.input-otp-description')) === null || _a === void 0 ? void 0 : _a.textContent) === null || _b === void 0 ? void 0 : _b.trim()) !== '';
|
|
565
|
-
return (h(Host, { key: '
|
|
604
|
+
return (h(Host, { key: '37d7d3663a6d2450862ba2e14796460dbb89db20', class: createColorClasses(color, {
|
|
566
605
|
[mode]: true,
|
|
567
606
|
'has-focus': hasFocus,
|
|
568
607
|
[`input-otp-size-${size}`]: true,
|
|
@@ -570,10 +609,10 @@ const InputOTP = class {
|
|
|
570
609
|
[`input-otp-fill-${fill}`]: true,
|
|
571
610
|
'input-otp-disabled': disabled,
|
|
572
611
|
'input-otp-readonly': readonly,
|
|
573
|
-
}) }, h("div", Object.assign({ key: '
|
|
612
|
+
}) }, h("div", Object.assign({ key: '1cf5d9e680229adc0f918d84143dbcd557bb2ab2', role: "group", "aria-label": "One-time password input", class: "input-otp-group" }, inheritedAttributes), Array.from({ length }).map((_, index) => (h(Fragment, null, h("div", { class: "native-wrapper" }, h("input", { class: "native-input", id: `${inputId}-${index}`, "aria-label": `Input ${index + 1} of ${length}`, type: "text", autoCapitalize: autocapitalize, inputmode: inputmode, pattern: pattern, disabled: disabled, readOnly: readonly, tabIndex: index === tabbableIndex ? 0 : -1, value: inputValues[index] || '', autocomplete: "one-time-code", ref: (el) => (inputRefs[index] = el), onInput: this.onInput(index), onBlur: this.onBlur, onFocus: this.onFocus(index), onKeyDown: this.onKeyDown(index), onPaste: this.onPaste })), this.showSeparator(index) && h("div", { class: "input-otp-separator" }))))), h("div", { key: '646ad36d4d49339c502ebca8c7bd81763ff54119', class: {
|
|
574
613
|
'input-otp-description': true,
|
|
575
614
|
'input-otp-description-hidden': !hasDescription,
|
|
576
|
-
} }, h("slot", { key: '
|
|
615
|
+
} }, h("slot", { key: 'b3594ae5455ec09868da0a7b76835157e921949a' }))));
|
|
577
616
|
}
|
|
578
617
|
get el() { return getElement(this); }
|
|
579
618
|
static get watchers() { return {
|