@ionic/core 8.6.5-nightly.20250710 → 8.6.5-nightly.20250714
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 +103 -50
- package/components/modal.js +36 -4
- package/dist/cjs/ion-input-otp.cjs.entry.js +102 -50
- package/dist/cjs/ion-modal.cjs.entry.js +36 -4
- package/dist/cjs/ionic.cjs.js +1 -1
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/collection/components/input-otp/input-otp.js +104 -51
- package/dist/collection/components/modal/modal.js +36 -4
- package/dist/docs.json +1 -1
- package/dist/esm/ion-input-otp.entry.js +102 -50
- package/dist/esm/ion-modal.entry.js +36 -4
- 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-29f784e8.entry.js +4 -0
- package/dist/ionic/p-35c4a63f.entry.js +4 -0
- package/dist/types/components/input-otp/input-otp.d.ts +12 -8
- package/dist/types/components/modal/modal.d.ts +9 -0
- package/hydrate/index.js +139 -54
- package/hydrate/index.mjs +139 -54
- package/package.json +1 -1
- package/dist/ionic/p-5c88e4c6.entry.js +0 -4
- package/dist/ionic/p-e30ff968.entry.js +0 -4
|
@@ -33,6 +33,7 @@ const InputOTP = /*@__PURE__*/ proxyCustomElement(class InputOTP extends HTMLEle
|
|
|
33
33
|
this.isKeyboardNavigation = false;
|
|
34
34
|
this.inputValues = [];
|
|
35
35
|
this.hasFocus = false;
|
|
36
|
+
this.previousInputValues = [];
|
|
36
37
|
/**
|
|
37
38
|
* Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
|
|
38
39
|
* Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
|
|
@@ -133,19 +134,12 @@ const InputOTP = /*@__PURE__*/ proxyCustomElement(class InputOTP extends HTMLEle
|
|
|
133
134
|
}
|
|
134
135
|
};
|
|
135
136
|
/**
|
|
136
|
-
* Handles keyboard navigation
|
|
137
|
+
* Handles keyboard navigation for the OTP component.
|
|
137
138
|
*
|
|
138
139
|
* Navigation:
|
|
139
140
|
* - Backspace: Clears current input and moves to previous box if empty
|
|
140
141
|
* - Arrow Left/Right: Moves focus between input boxes
|
|
141
142
|
* - Tab: Allows normal tab navigation between components
|
|
142
|
-
*
|
|
143
|
-
* Input Behavior:
|
|
144
|
-
* - Validates input against the allowed pattern
|
|
145
|
-
* - When entering a key in a filled box:
|
|
146
|
-
* - Shifts existing values right if there is room
|
|
147
|
-
* - Updates the value of the input group
|
|
148
|
-
* - Prevents default behavior to avoid automatic focus shift
|
|
149
143
|
*/
|
|
150
144
|
this.onKeyDown = (index) => (event) => {
|
|
151
145
|
const { length } = this;
|
|
@@ -200,30 +194,31 @@ const InputOTP = /*@__PURE__*/ proxyCustomElement(class InputOTP extends HTMLEle
|
|
|
200
194
|
// Let all tab events proceed normally
|
|
201
195
|
return;
|
|
202
196
|
}
|
|
203
|
-
// If the input box contains a value and the key being
|
|
204
|
-
// entered is a valid key for the input box update the value
|
|
205
|
-
// and shift the values to the right if there is room.
|
|
206
|
-
if (this.inputValues[index] && this.validKeyPattern.test(event.key)) {
|
|
207
|
-
if (!this.inputValues[length - 1]) {
|
|
208
|
-
for (let i = length - 1; i > index; i--) {
|
|
209
|
-
this.inputValues[i] = this.inputValues[i - 1];
|
|
210
|
-
this.inputRefs[i].value = this.inputValues[i] || '';
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
this.inputValues[index] = event.key;
|
|
214
|
-
this.inputRefs[index].value = event.key;
|
|
215
|
-
this.updateValue(event);
|
|
216
|
-
// Prevent default to avoid the browser from
|
|
217
|
-
// automatically moving the focus to the next input
|
|
218
|
-
event.preventDefault();
|
|
219
|
-
}
|
|
220
197
|
};
|
|
198
|
+
/**
|
|
199
|
+
* Processes all input scenarios for each input box.
|
|
200
|
+
*
|
|
201
|
+
* This function manages:
|
|
202
|
+
* 1. Autofill handling
|
|
203
|
+
* 2. Input validation
|
|
204
|
+
* 3. Full selection replacement or typing in an empty box
|
|
205
|
+
* 4. Inserting in the middle with available space (shifting)
|
|
206
|
+
* 5. Single character replacement
|
|
207
|
+
*/
|
|
221
208
|
this.onInput = (index) => (event) => {
|
|
209
|
+
var _a, _b;
|
|
222
210
|
const { length, validKeyPattern } = this;
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
211
|
+
const input = event.target;
|
|
212
|
+
const value = input.value;
|
|
213
|
+
const previousValue = this.previousInputValues[index] || '';
|
|
214
|
+
// 1. Autofill handling
|
|
215
|
+
// If the length of the value increases by more than 1 from the previous
|
|
216
|
+
// value, treat this as autofill. This is to prevent the case where the
|
|
217
|
+
// user is typing a single character into an input box containing a value
|
|
218
|
+
// as that will trigger this function with a value length of 2 characters.
|
|
219
|
+
const isAutofill = value.length - previousValue.length > 1;
|
|
220
|
+
if (isAutofill) {
|
|
221
|
+
// Distribute valid characters across input boxes
|
|
227
222
|
const validChars = value
|
|
228
223
|
.split('')
|
|
229
224
|
.filter((char) => validKeyPattern.test(char))
|
|
@@ -238,8 +233,10 @@ const InputOTP = /*@__PURE__*/ proxyCustomElement(class InputOTP extends HTMLEle
|
|
|
238
233
|
});
|
|
239
234
|
});
|
|
240
235
|
}
|
|
241
|
-
|
|
242
|
-
|
|
236
|
+
for (let i = 0; i < length; i++) {
|
|
237
|
+
this.inputValues[i] = validChars[i] || '';
|
|
238
|
+
this.inputRefs[i].value = validChars[i] || '';
|
|
239
|
+
}
|
|
243
240
|
this.updateValue(event);
|
|
244
241
|
// Focus the first empty input box or the last input box if all boxes
|
|
245
242
|
// are filled after a small delay to ensure the input boxes have been
|
|
@@ -249,20 +246,79 @@ const InputOTP = /*@__PURE__*/ proxyCustomElement(class InputOTP extends HTMLEle
|
|
|
249
246
|
const nextIndex = validChars.length < length ? validChars.length : length - 1;
|
|
250
247
|
(_a = this.inputRefs[nextIndex]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
251
248
|
}, 20);
|
|
249
|
+
this.previousInputValues = [...this.inputValues];
|
|
252
250
|
return;
|
|
253
251
|
}
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
252
|
+
// 2. Input validation
|
|
253
|
+
// If the character entered is invalid (does not match the pattern),
|
|
254
|
+
// restore the previous value and exit
|
|
255
|
+
if (value.length > 0 && !validKeyPattern.test(value[value.length - 1])) {
|
|
256
|
+
input.value = this.inputValues[index] || '';
|
|
257
|
+
this.previousInputValues = [...this.inputValues];
|
|
258
258
|
return;
|
|
259
259
|
}
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
this.
|
|
263
|
-
|
|
260
|
+
// 3. Full selection replacement or typing in an empty box
|
|
261
|
+
// If the user selects all text in the input box and types, or if the
|
|
262
|
+
// input box is empty, replace only this input box. If the box is empty,
|
|
263
|
+
// move to the next box, otherwise stay focused on this box.
|
|
264
|
+
const isAllSelected = input.selectionStart === 0 && input.selectionEnd === value.length;
|
|
265
|
+
const isEmpty = !this.inputValues[index];
|
|
266
|
+
if (isAllSelected || isEmpty) {
|
|
267
|
+
this.inputValues[index] = value;
|
|
268
|
+
input.value = value;
|
|
269
|
+
this.updateValue(event);
|
|
264
270
|
this.focusNext(index);
|
|
271
|
+
this.previousInputValues = [...this.inputValues];
|
|
272
|
+
return;
|
|
265
273
|
}
|
|
274
|
+
// 4. Inserting in the middle with available space (shifting)
|
|
275
|
+
// If typing in a filled input box and there are empty boxes at the end,
|
|
276
|
+
// shift all values starting at the current box to the right, and insert
|
|
277
|
+
// the new character at the current box.
|
|
278
|
+
const hasAvailableBoxAtEnd = this.inputValues[this.inputValues.length - 1] === '';
|
|
279
|
+
if (this.inputValues[index] && hasAvailableBoxAtEnd && value.length === 2) {
|
|
280
|
+
// Get the inserted character (from event or by diffing value/previousValue)
|
|
281
|
+
let newChar = event.data;
|
|
282
|
+
if (!newChar) {
|
|
283
|
+
newChar = value.split('').find((c, i) => c !== previousValue[i]) || value[value.length - 1];
|
|
284
|
+
}
|
|
285
|
+
// Validate the new character before shifting
|
|
286
|
+
if (!validKeyPattern.test(newChar)) {
|
|
287
|
+
input.value = this.inputValues[index] || '';
|
|
288
|
+
this.previousInputValues = [...this.inputValues];
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
// Shift values right from the end to the insertion point
|
|
292
|
+
for (let i = this.inputValues.length - 1; i > index; i--) {
|
|
293
|
+
this.inputValues[i] = this.inputValues[i - 1];
|
|
294
|
+
this.inputRefs[i].value = this.inputValues[i] || '';
|
|
295
|
+
}
|
|
296
|
+
this.inputValues[index] = newChar;
|
|
297
|
+
this.inputRefs[index].value = newChar;
|
|
298
|
+
this.updateValue(event);
|
|
299
|
+
this.previousInputValues = [...this.inputValues];
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
// 5. Single character replacement
|
|
303
|
+
// Handles replacing a single character in a box containing a value based
|
|
304
|
+
// on the cursor position. We need the cursor position to determine which
|
|
305
|
+
// character was the last character typed. For example, if the user types "2"
|
|
306
|
+
// in an input box with the cursor at the beginning of the value of "6",
|
|
307
|
+
// the value will be "26", but we want to grab the "2" as the last character
|
|
308
|
+
// typed.
|
|
309
|
+
const cursorPos = (_a = input.selectionStart) !== null && _a !== void 0 ? _a : value.length;
|
|
310
|
+
const newCharIndex = cursorPos - 1;
|
|
311
|
+
const newChar = (_b = value[newCharIndex]) !== null && _b !== void 0 ? _b : value[0];
|
|
312
|
+
// Check if the new character is valid before updating the value
|
|
313
|
+
if (!validKeyPattern.test(newChar)) {
|
|
314
|
+
input.value = this.inputValues[index] || '';
|
|
315
|
+
this.previousInputValues = [...this.inputValues];
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
this.inputValues[index] = newChar;
|
|
319
|
+
input.value = newChar;
|
|
320
|
+
this.updateValue(event);
|
|
321
|
+
this.previousInputValues = [...this.inputValues];
|
|
266
322
|
};
|
|
267
323
|
/**
|
|
268
324
|
* Handles pasting text into the input OTP component.
|
|
@@ -272,7 +328,7 @@ const InputOTP = /*@__PURE__*/ proxyCustomElement(class InputOTP extends HTMLEle
|
|
|
272
328
|
* the next empty input after pasting.
|
|
273
329
|
*/
|
|
274
330
|
this.onPaste = (event) => {
|
|
275
|
-
var _a, _b
|
|
331
|
+
var _a, _b;
|
|
276
332
|
const { inputRefs, length, validKeyPattern } = this;
|
|
277
333
|
event.preventDefault();
|
|
278
334
|
const pastedText = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text');
|
|
@@ -299,13 +355,8 @@ const InputOTP = /*@__PURE__*/ proxyCustomElement(class InputOTP extends HTMLEle
|
|
|
299
355
|
this.updateValue(event);
|
|
300
356
|
// Focus the next empty input after pasting
|
|
301
357
|
// If all boxes are filled, focus the last input
|
|
302
|
-
const nextEmptyIndex = validChars.length;
|
|
303
|
-
|
|
304
|
-
(_b = inputRefs[nextEmptyIndex]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
(_c = inputRefs[length - 1]) === null || _c === void 0 ? void 0 : _c.focus();
|
|
308
|
-
}
|
|
358
|
+
const nextEmptyIndex = validChars.length < length ? validChars.length : length - 1;
|
|
359
|
+
(_b = inputRefs[nextEmptyIndex]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
309
360
|
};
|
|
310
361
|
}
|
|
311
362
|
/**
|
|
@@ -444,6 +495,7 @@ const InputOTP = /*@__PURE__*/ proxyCustomElement(class InputOTP extends HTMLEle
|
|
|
444
495
|
});
|
|
445
496
|
// Update the value without emitting events
|
|
446
497
|
this.value = this.inputValues.join('');
|
|
498
|
+
this.previousInputValues = [...this.inputValues];
|
|
447
499
|
}
|
|
448
500
|
/**
|
|
449
501
|
* Updates the value of the input group.
|
|
@@ -565,7 +617,7 @@ const InputOTP = /*@__PURE__*/ proxyCustomElement(class InputOTP extends HTMLEle
|
|
|
565
617
|
const tabbableIndex = this.getTabbableIndex();
|
|
566
618
|
const pattern = this.getPattern();
|
|
567
619
|
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()) !== '';
|
|
568
|
-
return (h(Host, { key: '
|
|
620
|
+
return (h(Host, { key: 'f15a29fb17b681ef55885ca36d3d5f899cbaca83', class: createColorClasses(color, {
|
|
569
621
|
[mode]: true,
|
|
570
622
|
'has-focus': hasFocus,
|
|
571
623
|
[`input-otp-size-${size}`]: true,
|
|
@@ -573,10 +625,10 @@ const InputOTP = /*@__PURE__*/ proxyCustomElement(class InputOTP extends HTMLEle
|
|
|
573
625
|
[`input-otp-fill-${fill}`]: true,
|
|
574
626
|
'input-otp-disabled': disabled,
|
|
575
627
|
'input-otp-readonly': readonly,
|
|
576
|
-
}) }, h("div", Object.assign({ key: '
|
|
628
|
+
}) }, h("div", Object.assign({ key: 'd7e1d4edd8aafcf2ed4313301287282e90fc7e82', 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: '3724a3159d02860971879a906092f9965f5a7c47', class: {
|
|
577
629
|
'input-otp-description': true,
|
|
578
630
|
'input-otp-description-hidden': !hasDescription,
|
|
579
|
-
} }, h("slot", { key: '
|
|
631
|
+
} }, h("slot", { key: '11baa2624926a08274508afe0833d9237a8dc35c' }))));
|
|
580
632
|
}
|
|
581
633
|
get el() { return this; }
|
|
582
634
|
static get watchers() { return {
|
|
@@ -604,6 +656,7 @@ const InputOTP = /*@__PURE__*/ proxyCustomElement(class InputOTP extends HTMLEle
|
|
|
604
656
|
"value": [1032],
|
|
605
657
|
"inputValues": [32],
|
|
606
658
|
"hasFocus": [32],
|
|
659
|
+
"previousInputValues": [32],
|
|
607
660
|
"setFocus": [64]
|
|
608
661
|
}, undefined, {
|
|
609
662
|
"value": ["valueChanged"],
|
package/components/modal.js
CHANGED
|
@@ -1652,6 +1652,26 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
1652
1652
|
dragHandleEl.focus();
|
|
1653
1653
|
}
|
|
1654
1654
|
};
|
|
1655
|
+
/**
|
|
1656
|
+
* When the slot changes, we need to find all the modals in the slot
|
|
1657
|
+
* and set the data-parent-ion-modal attribute on them so we can find them
|
|
1658
|
+
* and dismiss them when we get dismissed.
|
|
1659
|
+
* We need to do it this way because when a modal is opened, it's moved to
|
|
1660
|
+
* the end of the body and is no longer an actual child of the modal.
|
|
1661
|
+
*/
|
|
1662
|
+
this.onSlotChange = ({ target }) => {
|
|
1663
|
+
const slot = target;
|
|
1664
|
+
slot.assignedElements().forEach((el) => {
|
|
1665
|
+
el.querySelectorAll('ion-modal').forEach((childModal) => {
|
|
1666
|
+
// We don't need to write to the DOM if the modal is already tagged
|
|
1667
|
+
// If this is a deeply nested modal, this effect should cascade so we don't
|
|
1668
|
+
// need to worry about another modal claiming the same child.
|
|
1669
|
+
if (childModal.getAttribute('data-parent-ion-modal') === null) {
|
|
1670
|
+
childModal.setAttribute('data-parent-ion-modal', this.el.id);
|
|
1671
|
+
}
|
|
1672
|
+
});
|
|
1673
|
+
});
|
|
1674
|
+
};
|
|
1655
1675
|
}
|
|
1656
1676
|
onIsOpenChange(newValue, oldValue) {
|
|
1657
1677
|
if (newValue === true && oldValue === false) {
|
|
@@ -2015,6 +2035,12 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2015
2035
|
* in case the dismiss transition does run.
|
|
2016
2036
|
*/
|
|
2017
2037
|
const unlock = await this.lockController.lock();
|
|
2038
|
+
/**
|
|
2039
|
+
* Dismiss all child modals. This is especially important in
|
|
2040
|
+
* Angular and React because it's possible to lose control of a child
|
|
2041
|
+
* modal when the parent modal is dismissed.
|
|
2042
|
+
*/
|
|
2043
|
+
await this.dismissNestedModals();
|
|
2018
2044
|
/**
|
|
2019
2045
|
* If a canDismiss handler is responsible
|
|
2020
2046
|
* for calling the dismiss method, we should
|
|
@@ -2233,6 +2259,12 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2233
2259
|
}
|
|
2234
2260
|
}
|
|
2235
2261
|
}
|
|
2262
|
+
async dismissNestedModals() {
|
|
2263
|
+
const nestedModals = document.querySelectorAll(`ion-modal[data-parent-ion-modal="${this.el.id}"]`);
|
|
2264
|
+
nestedModals === null || nestedModals === void 0 ? void 0 : nestedModals.forEach(async (modal) => {
|
|
2265
|
+
await modal.dismiss(undefined, 'parent-dismissed');
|
|
2266
|
+
});
|
|
2267
|
+
}
|
|
2236
2268
|
render() {
|
|
2237
2269
|
const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
|
|
2238
2270
|
const showHandle = handle !== false && isSheetModal;
|
|
@@ -2240,20 +2272,20 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2240
2272
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
2241
2273
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
2242
2274
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
2243
|
-
return (h(Host, Object.assign({ key: '
|
|
2275
|
+
return (h(Host, Object.assign({ key: '4cb27cda1c331191ccc623e15a999e524d053397', "no-router": true,
|
|
2244
2276
|
// Allow the modal to be navigable when the handle is focusable
|
|
2245
2277
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
2246
2278
|
zIndex: `${20000 + this.overlayIndex}`,
|
|
2247
|
-
}, 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: '
|
|
2279
|
+
}, 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: '5de598f6aaad693da61a8cdfd8ea73e3d9cc762e', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '2e74d2ed93c7884cbb1e3561d7296bba46e10426', class: "modal-shadow" }), h("div", Object.assign({ key: '314a7f8f669639b2d3f3a61a86e3b9de31a3ec78',
|
|
2248
2280
|
/*
|
|
2249
2281
|
role and aria-modal must be used on the
|
|
2250
2282
|
same element. They must also be set inside the
|
|
2251
2283
|
shadow DOM otherwise ion-button will not be highlighted
|
|
2252
2284
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
2253
2285
|
*/
|
|
2254
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '
|
|
2286
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: 'fc16ab670165f5ecc6b27b026637094da16d7af4', class: "modal-handle",
|
|
2255
2287
|
// Prevents the handle from receiving keyboard focus when it does not cycle
|
|
2256
|
-
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: '
|
|
2288
|
+
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: 'e3634c590f877dc34d39d651c059fa3f701cfb4d', onSlotchange: this.onSlotChange }))));
|
|
2257
2289
|
}
|
|
2258
2290
|
get el() { return this; }
|
|
2259
2291
|
static get watchers() { return {
|
|
@@ -32,6 +32,7 @@ const InputOTP = class {
|
|
|
32
32
|
this.isKeyboardNavigation = false;
|
|
33
33
|
this.inputValues = [];
|
|
34
34
|
this.hasFocus = false;
|
|
35
|
+
this.previousInputValues = [];
|
|
35
36
|
/**
|
|
36
37
|
* Indicates whether and how the text value should be automatically capitalized as it is entered/edited by the user.
|
|
37
38
|
* Available options: `"off"`, `"none"`, `"on"`, `"sentences"`, `"words"`, `"characters"`.
|
|
@@ -132,19 +133,12 @@ const InputOTP = class {
|
|
|
132
133
|
}
|
|
133
134
|
};
|
|
134
135
|
/**
|
|
135
|
-
* Handles keyboard navigation
|
|
136
|
+
* Handles keyboard navigation for the OTP component.
|
|
136
137
|
*
|
|
137
138
|
* Navigation:
|
|
138
139
|
* - Backspace: Clears current input and moves to previous box if empty
|
|
139
140
|
* - Arrow Left/Right: Moves focus between input boxes
|
|
140
141
|
* - Tab: Allows normal tab navigation between components
|
|
141
|
-
*
|
|
142
|
-
* Input Behavior:
|
|
143
|
-
* - Validates input against the allowed pattern
|
|
144
|
-
* - When entering a key in a filled box:
|
|
145
|
-
* - Shifts existing values right if there is room
|
|
146
|
-
* - Updates the value of the input group
|
|
147
|
-
* - Prevents default behavior to avoid automatic focus shift
|
|
148
142
|
*/
|
|
149
143
|
this.onKeyDown = (index) => (event) => {
|
|
150
144
|
const { length } = this;
|
|
@@ -199,30 +193,31 @@ const InputOTP = class {
|
|
|
199
193
|
// Let all tab events proceed normally
|
|
200
194
|
return;
|
|
201
195
|
}
|
|
202
|
-
// If the input box contains a value and the key being
|
|
203
|
-
// entered is a valid key for the input box update the value
|
|
204
|
-
// and shift the values to the right if there is room.
|
|
205
|
-
if (this.inputValues[index] && this.validKeyPattern.test(event.key)) {
|
|
206
|
-
if (!this.inputValues[length - 1]) {
|
|
207
|
-
for (let i = length - 1; i > index; i--) {
|
|
208
|
-
this.inputValues[i] = this.inputValues[i - 1];
|
|
209
|
-
this.inputRefs[i].value = this.inputValues[i] || '';
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
this.inputValues[index] = event.key;
|
|
213
|
-
this.inputRefs[index].value = event.key;
|
|
214
|
-
this.updateValue(event);
|
|
215
|
-
// Prevent default to avoid the browser from
|
|
216
|
-
// automatically moving the focus to the next input
|
|
217
|
-
event.preventDefault();
|
|
218
|
-
}
|
|
219
196
|
};
|
|
197
|
+
/**
|
|
198
|
+
* Processes all input scenarios for each input box.
|
|
199
|
+
*
|
|
200
|
+
* This function manages:
|
|
201
|
+
* 1. Autofill handling
|
|
202
|
+
* 2. Input validation
|
|
203
|
+
* 3. Full selection replacement or typing in an empty box
|
|
204
|
+
* 4. Inserting in the middle with available space (shifting)
|
|
205
|
+
* 5. Single character replacement
|
|
206
|
+
*/
|
|
220
207
|
this.onInput = (index) => (event) => {
|
|
208
|
+
var _a, _b;
|
|
221
209
|
const { length, validKeyPattern } = this;
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
210
|
+
const input = event.target;
|
|
211
|
+
const value = input.value;
|
|
212
|
+
const previousValue = this.previousInputValues[index] || '';
|
|
213
|
+
// 1. Autofill handling
|
|
214
|
+
// If the length of the value increases by more than 1 from the previous
|
|
215
|
+
// value, treat this as autofill. This is to prevent the case where the
|
|
216
|
+
// user is typing a single character into an input box containing a value
|
|
217
|
+
// as that will trigger this function with a value length of 2 characters.
|
|
218
|
+
const isAutofill = value.length - previousValue.length > 1;
|
|
219
|
+
if (isAutofill) {
|
|
220
|
+
// Distribute valid characters across input boxes
|
|
226
221
|
const validChars = value
|
|
227
222
|
.split('')
|
|
228
223
|
.filter((char) => validKeyPattern.test(char))
|
|
@@ -237,8 +232,10 @@ const InputOTP = class {
|
|
|
237
232
|
});
|
|
238
233
|
});
|
|
239
234
|
}
|
|
240
|
-
|
|
241
|
-
|
|
235
|
+
for (let i = 0; i < length; i++) {
|
|
236
|
+
this.inputValues[i] = validChars[i] || '';
|
|
237
|
+
this.inputRefs[i].value = validChars[i] || '';
|
|
238
|
+
}
|
|
242
239
|
this.updateValue(event);
|
|
243
240
|
// Focus the first empty input box or the last input box if all boxes
|
|
244
241
|
// are filled after a small delay to ensure the input boxes have been
|
|
@@ -248,20 +245,79 @@ const InputOTP = class {
|
|
|
248
245
|
const nextIndex = validChars.length < length ? validChars.length : length - 1;
|
|
249
246
|
(_a = this.inputRefs[nextIndex]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
250
247
|
}, 20);
|
|
248
|
+
this.previousInputValues = [...this.inputValues];
|
|
251
249
|
return;
|
|
252
250
|
}
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
251
|
+
// 2. Input validation
|
|
252
|
+
// If the character entered is invalid (does not match the pattern),
|
|
253
|
+
// restore the previous value and exit
|
|
254
|
+
if (value.length > 0 && !validKeyPattern.test(value[value.length - 1])) {
|
|
255
|
+
input.value = this.inputValues[index] || '';
|
|
256
|
+
this.previousInputValues = [...this.inputValues];
|
|
257
257
|
return;
|
|
258
258
|
}
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
this.
|
|
262
|
-
|
|
259
|
+
// 3. Full selection replacement or typing in an empty box
|
|
260
|
+
// If the user selects all text in the input box and types, or if the
|
|
261
|
+
// input box is empty, replace only this input box. If the box is empty,
|
|
262
|
+
// move to the next box, otherwise stay focused on this box.
|
|
263
|
+
const isAllSelected = input.selectionStart === 0 && input.selectionEnd === value.length;
|
|
264
|
+
const isEmpty = !this.inputValues[index];
|
|
265
|
+
if (isAllSelected || isEmpty) {
|
|
266
|
+
this.inputValues[index] = value;
|
|
267
|
+
input.value = value;
|
|
268
|
+
this.updateValue(event);
|
|
263
269
|
this.focusNext(index);
|
|
270
|
+
this.previousInputValues = [...this.inputValues];
|
|
271
|
+
return;
|
|
264
272
|
}
|
|
273
|
+
// 4. Inserting in the middle with available space (shifting)
|
|
274
|
+
// If typing in a filled input box and there are empty boxes at the end,
|
|
275
|
+
// shift all values starting at the current box to the right, and insert
|
|
276
|
+
// the new character at the current box.
|
|
277
|
+
const hasAvailableBoxAtEnd = this.inputValues[this.inputValues.length - 1] === '';
|
|
278
|
+
if (this.inputValues[index] && hasAvailableBoxAtEnd && value.length === 2) {
|
|
279
|
+
// Get the inserted character (from event or by diffing value/previousValue)
|
|
280
|
+
let newChar = event.data;
|
|
281
|
+
if (!newChar) {
|
|
282
|
+
newChar = value.split('').find((c, i) => c !== previousValue[i]) || value[value.length - 1];
|
|
283
|
+
}
|
|
284
|
+
// Validate the new character before shifting
|
|
285
|
+
if (!validKeyPattern.test(newChar)) {
|
|
286
|
+
input.value = this.inputValues[index] || '';
|
|
287
|
+
this.previousInputValues = [...this.inputValues];
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
// Shift values right from the end to the insertion point
|
|
291
|
+
for (let i = this.inputValues.length - 1; i > index; i--) {
|
|
292
|
+
this.inputValues[i] = this.inputValues[i - 1];
|
|
293
|
+
this.inputRefs[i].value = this.inputValues[i] || '';
|
|
294
|
+
}
|
|
295
|
+
this.inputValues[index] = newChar;
|
|
296
|
+
this.inputRefs[index].value = newChar;
|
|
297
|
+
this.updateValue(event);
|
|
298
|
+
this.previousInputValues = [...this.inputValues];
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
// 5. Single character replacement
|
|
302
|
+
// Handles replacing a single character in a box containing a value based
|
|
303
|
+
// on the cursor position. We need the cursor position to determine which
|
|
304
|
+
// character was the last character typed. For example, if the user types "2"
|
|
305
|
+
// in an input box with the cursor at the beginning of the value of "6",
|
|
306
|
+
// the value will be "26", but we want to grab the "2" as the last character
|
|
307
|
+
// typed.
|
|
308
|
+
const cursorPos = (_a = input.selectionStart) !== null && _a !== void 0 ? _a : value.length;
|
|
309
|
+
const newCharIndex = cursorPos - 1;
|
|
310
|
+
const newChar = (_b = value[newCharIndex]) !== null && _b !== void 0 ? _b : value[0];
|
|
311
|
+
// Check if the new character is valid before updating the value
|
|
312
|
+
if (!validKeyPattern.test(newChar)) {
|
|
313
|
+
input.value = this.inputValues[index] || '';
|
|
314
|
+
this.previousInputValues = [...this.inputValues];
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
this.inputValues[index] = newChar;
|
|
318
|
+
input.value = newChar;
|
|
319
|
+
this.updateValue(event);
|
|
320
|
+
this.previousInputValues = [...this.inputValues];
|
|
265
321
|
};
|
|
266
322
|
/**
|
|
267
323
|
* Handles pasting text into the input OTP component.
|
|
@@ -271,7 +327,7 @@ const InputOTP = class {
|
|
|
271
327
|
* the next empty input after pasting.
|
|
272
328
|
*/
|
|
273
329
|
this.onPaste = (event) => {
|
|
274
|
-
var _a, _b
|
|
330
|
+
var _a, _b;
|
|
275
331
|
const { inputRefs, length, validKeyPattern } = this;
|
|
276
332
|
event.preventDefault();
|
|
277
333
|
const pastedText = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text');
|
|
@@ -298,13 +354,8 @@ const InputOTP = class {
|
|
|
298
354
|
this.updateValue(event);
|
|
299
355
|
// Focus the next empty input after pasting
|
|
300
356
|
// If all boxes are filled, focus the last input
|
|
301
|
-
const nextEmptyIndex = validChars.length;
|
|
302
|
-
|
|
303
|
-
(_b = inputRefs[nextEmptyIndex]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
(_c = inputRefs[length - 1]) === null || _c === void 0 ? void 0 : _c.focus();
|
|
307
|
-
}
|
|
357
|
+
const nextEmptyIndex = validChars.length < length ? validChars.length : length - 1;
|
|
358
|
+
(_b = inputRefs[nextEmptyIndex]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
308
359
|
};
|
|
309
360
|
}
|
|
310
361
|
/**
|
|
@@ -443,6 +494,7 @@ const InputOTP = class {
|
|
|
443
494
|
});
|
|
444
495
|
// Update the value without emitting events
|
|
445
496
|
this.value = this.inputValues.join('');
|
|
497
|
+
this.previousInputValues = [...this.inputValues];
|
|
446
498
|
}
|
|
447
499
|
/**
|
|
448
500
|
* Updates the value of the input group.
|
|
@@ -564,7 +616,7 @@ const InputOTP = class {
|
|
|
564
616
|
const tabbableIndex = this.getTabbableIndex();
|
|
565
617
|
const pattern = this.getPattern();
|
|
566
618
|
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()) !== '';
|
|
567
|
-
return (index.h(index.Host, { key: '
|
|
619
|
+
return (index.h(index.Host, { key: 'f15a29fb17b681ef55885ca36d3d5f899cbaca83', class: theme.createColorClasses(color, {
|
|
568
620
|
[mode]: true,
|
|
569
621
|
'has-focus': hasFocus,
|
|
570
622
|
[`input-otp-size-${size}`]: true,
|
|
@@ -572,10 +624,10 @@ const InputOTP = class {
|
|
|
572
624
|
[`input-otp-fill-${fill}`]: true,
|
|
573
625
|
'input-otp-disabled': disabled,
|
|
574
626
|
'input-otp-readonly': readonly,
|
|
575
|
-
}) }, index.h("div", Object.assign({ key: '
|
|
627
|
+
}) }, index.h("div", Object.assign({ key: 'd7e1d4edd8aafcf2ed4313301287282e90fc7e82', role: "group", "aria-label": "One-time password input", class: "input-otp-group" }, inheritedAttributes), Array.from({ length }).map((_, index$1) => (index.h(index.Fragment, null, index.h("div", { class: "native-wrapper" }, index.h("input", { class: "native-input", id: `${inputId}-${index$1}`, "aria-label": `Input ${index$1 + 1} of ${length}`, type: "text", autoCapitalize: autocapitalize, inputmode: inputmode, pattern: pattern, disabled: disabled, readOnly: readonly, tabIndex: index$1 === tabbableIndex ? 0 : -1, value: inputValues[index$1] || '', autocomplete: "one-time-code", ref: (el) => (inputRefs[index$1] = el), onInput: this.onInput(index$1), onBlur: this.onBlur, onFocus: this.onFocus(index$1), onKeyDown: this.onKeyDown(index$1), onPaste: this.onPaste })), this.showSeparator(index$1) && index.h("div", { class: "input-otp-separator" }))))), index.h("div", { key: '3724a3159d02860971879a906092f9965f5a7c47', class: {
|
|
576
628
|
'input-otp-description': true,
|
|
577
629
|
'input-otp-description-hidden': !hasDescription,
|
|
578
|
-
} }, index.h("slot", { key: '
|
|
630
|
+
} }, index.h("slot", { key: '11baa2624926a08274508afe0833d9237a8dc35c' }))));
|
|
579
631
|
}
|
|
580
632
|
get el() { return index.getElement(this); }
|
|
581
633
|
static get watchers() { return {
|