@ionic/core 8.6.5-dev.11752242329.17d249a3 → 8.6.5-dev.11752243397.18475074
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 +118 -64
- package/components/ion-input.js +2 -2
- package/components/modal.js +4 -36
- package/dist/cjs/ion-input-otp.cjs.entry.js +117 -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 +119 -65
- package/dist/collection/components/modal/modal.js +4 -36
- package/dist/docs.json +1 -1
- package/dist/esm/ion-input-otp.entry.js +117 -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-77e2daa9.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 +124 -102
- package/hydrate/index.mjs +124 -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,132 @@ 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
|
-
|
|
223
|
-
|
|
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
|
+
if (this.inputRefs[i] != null) {
|
|
217
|
+
this.inputRefs[i].value = validChars[i] || '';
|
|
218
|
+
}
|
|
228
219
|
}
|
|
229
|
-
// Update the value of the input group and emit the input change event
|
|
230
|
-
this.value = validChars.join('');
|
|
231
220
|
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
|
|
221
|
+
// Focus the next empty input or the last one
|
|
235
222
|
setTimeout(() => {
|
|
236
|
-
var _a;
|
|
237
223
|
const nextIndex = validChars.length < length ? validChars.length : length - 1;
|
|
238
|
-
(
|
|
224
|
+
if (this.inputRefs[nextIndex] != null) {
|
|
225
|
+
this.inputRefs[nextIndex].focus();
|
|
226
|
+
}
|
|
239
227
|
}, 20);
|
|
228
|
+
this.previousInputValues = [...this.inputValues];
|
|
240
229
|
return;
|
|
241
230
|
}
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
231
|
+
// 2. Input validation
|
|
232
|
+
// If the character entered is invalid (does not match the pattern),
|
|
233
|
+
// restore the previous value and exit
|
|
234
|
+
if (value.length > 0 && !validKeyPattern.test(value[value.length - 1])) {
|
|
235
|
+
input.value = this.inputValues[index] || '';
|
|
236
|
+
this.previousInputValues[index] = this.inputValues[index] || '';
|
|
237
|
+
this.previousInputValues = [...this.inputValues];
|
|
246
238
|
return;
|
|
247
239
|
}
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
this.
|
|
251
|
-
|
|
240
|
+
// 3. Full selection replacement or typing in an empty box
|
|
241
|
+
// If the user selects all text in the input box and types, or if the
|
|
242
|
+
// input box is empty, replace only this input box. If the box is empty,
|
|
243
|
+
// move to the next box, otherwise stay focused on this box.
|
|
244
|
+
const isAllSelected = input.selectionStart === 0 && input.selectionEnd === value.length;
|
|
245
|
+
const isEmpty = !this.inputValues[index];
|
|
246
|
+
if (isAllSelected || isEmpty) {
|
|
247
|
+
this.inputValues[index] = value;
|
|
248
|
+
input.value = value;
|
|
249
|
+
this.previousInputValues[index] = value;
|
|
250
|
+
this.updateValue(event);
|
|
252
251
|
this.focusNext(index);
|
|
252
|
+
this.previousInputValues = [...this.inputValues];
|
|
253
|
+
return;
|
|
253
254
|
}
|
|
255
|
+
// 4. Inserting in the middle with available space (shifting)
|
|
256
|
+
// If typing in a filled input box and there are empty boxes at the end,
|
|
257
|
+
// shift all values starting at the current box to the right, and insert
|
|
258
|
+
// the new character at the current box.
|
|
259
|
+
const hasAvailableBoxAtEnd = this.inputValues[this.inputValues.length - 1] === '';
|
|
260
|
+
if (this.inputValues[index] && hasAvailableBoxAtEnd && value.length === 2) {
|
|
261
|
+
// Get the inserted character (from event or by diffing value/previousValue)
|
|
262
|
+
let newChar = event.data;
|
|
263
|
+
if (!newChar) {
|
|
264
|
+
newChar = value.split('').find((c, i) => c !== previousValue[i]) || value[value.length - 1];
|
|
265
|
+
}
|
|
266
|
+
// Validate the new character before shifting
|
|
267
|
+
if (!validKeyPattern.test(newChar)) {
|
|
268
|
+
input.value = this.inputValues[index] || '';
|
|
269
|
+
this.previousInputValues[index] = this.inputValues[index] || '';
|
|
270
|
+
this.previousInputValues = [...this.inputValues];
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
// Shift values right from the end to the insertion point
|
|
274
|
+
for (let i = this.inputValues.length - 1; i > index; i--) {
|
|
275
|
+
this.inputValues[i] = this.inputValues[i - 1];
|
|
276
|
+
if (this.inputRefs[i] != null) {
|
|
277
|
+
this.inputRefs[i].value = this.inputValues[i] || '';
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
this.inputValues[index] = newChar;
|
|
281
|
+
if (this.inputRefs[index] != null) {
|
|
282
|
+
this.inputRefs[index].value = newChar;
|
|
283
|
+
}
|
|
284
|
+
this.previousInputValues[index] = newChar;
|
|
285
|
+
this.updateValue(event);
|
|
286
|
+
this.previousInputValues = [...this.inputValues];
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
// 5. Single character replacement
|
|
290
|
+
// Handles replacing a single character in a box containing a value based
|
|
291
|
+
// on the cursor position. We need the cursor position to determine which
|
|
292
|
+
// character was the last character typed. For example, if the user types "2"
|
|
293
|
+
// in an input box with the cursor at the beginning of the value of "6",
|
|
294
|
+
// the value will be "26", but we want to grab the "2" as the last character
|
|
295
|
+
// typed.
|
|
296
|
+
const cursorPos = (_a = input.selectionStart) !== null && _a !== void 0 ? _a : value.length;
|
|
297
|
+
const newCharIndex = cursorPos - 1;
|
|
298
|
+
const newChar = (_b = value[newCharIndex]) !== null && _b !== void 0 ? _b : value[0];
|
|
299
|
+
// Check if the new character is valid before updating the value
|
|
300
|
+
if (!validKeyPattern.test(newChar)) {
|
|
301
|
+
input.value = this.inputValues[index] || '';
|
|
302
|
+
this.previousInputValues[index] = this.inputValues[index] || '';
|
|
303
|
+
this.previousInputValues = [...this.inputValues];
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
input.value = newChar;
|
|
307
|
+
this.inputValues[index] = newChar;
|
|
308
|
+
this.previousInputValues[index] = newChar;
|
|
309
|
+
this.updateValue(event);
|
|
310
|
+
this.previousInputValues = [...this.inputValues];
|
|
254
311
|
};
|
|
255
312
|
/**
|
|
256
313
|
* Handles pasting text into the input OTP component.
|
|
@@ -260,7 +317,7 @@ export class InputOTP {
|
|
|
260
317
|
* the next empty input after pasting.
|
|
261
318
|
*/
|
|
262
319
|
this.onPaste = (event) => {
|
|
263
|
-
var _a, _b
|
|
320
|
+
var _a, _b;
|
|
264
321
|
const { inputRefs, length, validKeyPattern } = this;
|
|
265
322
|
event.preventDefault();
|
|
266
323
|
const pastedText = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text');
|
|
@@ -287,13 +344,8 @@ export class InputOTP {
|
|
|
287
344
|
this.updateValue(event);
|
|
288
345
|
// Focus the next empty input after pasting
|
|
289
346
|
// 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
|
-
}
|
|
347
|
+
const nextEmptyIndex = validChars.length < length ? validChars.length : length - 1;
|
|
348
|
+
(_b = inputRefs[nextEmptyIndex]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
297
349
|
};
|
|
298
350
|
}
|
|
299
351
|
/**
|
|
@@ -432,6 +484,7 @@ export class InputOTP {
|
|
|
432
484
|
});
|
|
433
485
|
// Update the value without emitting events
|
|
434
486
|
this.value = this.inputValues.join('');
|
|
487
|
+
this.previousInputValues = [...this.inputValues];
|
|
435
488
|
}
|
|
436
489
|
/**
|
|
437
490
|
* Updates the value of the input group.
|
|
@@ -553,7 +606,7 @@ export class InputOTP {
|
|
|
553
606
|
const tabbableIndex = this.getTabbableIndex();
|
|
554
607
|
const pattern = this.getPattern();
|
|
555
608
|
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: '
|
|
609
|
+
return (h(Host, { key: '084b4f7d148a55aef6b4b51c11483ee51d70d3bd', class: createColorClasses(color, {
|
|
557
610
|
[mode]: true,
|
|
558
611
|
'has-focus': hasFocus,
|
|
559
612
|
[`input-otp-size-${size}`]: true,
|
|
@@ -561,10 +614,10 @@ export class InputOTP {
|
|
|
561
614
|
[`input-otp-fill-${fill}`]: true,
|
|
562
615
|
'input-otp-disabled': disabled,
|
|
563
616
|
'input-otp-readonly': readonly,
|
|
564
|
-
}) }, h("div", Object.assign({ key: '
|
|
617
|
+
}) }, h("div", Object.assign({ key: '9d797deb7170bf6e4cc1acf70cca0b5d4ef51610', 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: 'a0463205729699430560032a68ade2e2ffa49b61', class: {
|
|
565
618
|
'input-otp-description': true,
|
|
566
619
|
'input-otp-description-hidden': !hasDescription,
|
|
567
|
-
} }, h("slot", { key: '
|
|
620
|
+
} }, h("slot", { key: '287fdaf0375cda3dcfafa2762d7daebf6f2bfe68' }))));
|
|
568
621
|
}
|
|
569
622
|
static get is() { return "ion-input-otp"; }
|
|
570
623
|
static get encapsulation() { return "scoped"; }
|
|
@@ -849,7 +902,8 @@ export class InputOTP {
|
|
|
849
902
|
static get states() {
|
|
850
903
|
return {
|
|
851
904
|
"inputValues": {},
|
|
852
|
-
"hasFocus": {}
|
|
905
|
+
"hasFocus": {},
|
|
906
|
+
"previousInputValues": {}
|
|
853
907
|
};
|
|
854
908
|
}
|
|
855
909
|
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,132 @@ 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
|
-
|
|
232
|
-
|
|
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
|
+
if (this.inputRefs[i] != null) {
|
|
226
|
+
this.inputRefs[i].value = validChars[i] || '';
|
|
227
|
+
}
|
|
237
228
|
}
|
|
238
|
-
// Update the value of the input group and emit the input change event
|
|
239
|
-
this.value = validChars.join('');
|
|
240
229
|
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
|
|
230
|
+
// Focus the next empty input or the last one
|
|
244
231
|
setTimeout(() => {
|
|
245
|
-
var _a;
|
|
246
232
|
const nextIndex = validChars.length < length ? validChars.length : length - 1;
|
|
247
|
-
(
|
|
233
|
+
if (this.inputRefs[nextIndex] != null) {
|
|
234
|
+
this.inputRefs[nextIndex].focus();
|
|
235
|
+
}
|
|
248
236
|
}, 20);
|
|
237
|
+
this.previousInputValues = [...this.inputValues];
|
|
249
238
|
return;
|
|
250
239
|
}
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
240
|
+
// 2. Input validation
|
|
241
|
+
// If the character entered is invalid (does not match the pattern),
|
|
242
|
+
// restore the previous value and exit
|
|
243
|
+
if (value.length > 0 && !validKeyPattern.test(value[value.length - 1])) {
|
|
244
|
+
input.value = this.inputValues[index] || '';
|
|
245
|
+
this.previousInputValues[index] = this.inputValues[index] || '';
|
|
246
|
+
this.previousInputValues = [...this.inputValues];
|
|
255
247
|
return;
|
|
256
248
|
}
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
this.
|
|
260
|
-
|
|
249
|
+
// 3. Full selection replacement or typing in an empty box
|
|
250
|
+
// If the user selects all text in the input box and types, or if the
|
|
251
|
+
// input box is empty, replace only this input box. If the box is empty,
|
|
252
|
+
// move to the next box, otherwise stay focused on this box.
|
|
253
|
+
const isAllSelected = input.selectionStart === 0 && input.selectionEnd === value.length;
|
|
254
|
+
const isEmpty = !this.inputValues[index];
|
|
255
|
+
if (isAllSelected || isEmpty) {
|
|
256
|
+
this.inputValues[index] = value;
|
|
257
|
+
input.value = value;
|
|
258
|
+
this.previousInputValues[index] = value;
|
|
259
|
+
this.updateValue(event);
|
|
261
260
|
this.focusNext(index);
|
|
261
|
+
this.previousInputValues = [...this.inputValues];
|
|
262
|
+
return;
|
|
262
263
|
}
|
|
264
|
+
// 4. Inserting in the middle with available space (shifting)
|
|
265
|
+
// If typing in a filled input box and there are empty boxes at the end,
|
|
266
|
+
// shift all values starting at the current box to the right, and insert
|
|
267
|
+
// the new character at the current box.
|
|
268
|
+
const hasAvailableBoxAtEnd = this.inputValues[this.inputValues.length - 1] === '';
|
|
269
|
+
if (this.inputValues[index] && hasAvailableBoxAtEnd && value.length === 2) {
|
|
270
|
+
// Get the inserted character (from event or by diffing value/previousValue)
|
|
271
|
+
let newChar = event.data;
|
|
272
|
+
if (!newChar) {
|
|
273
|
+
newChar = value.split('').find((c, i) => c !== previousValue[i]) || value[value.length - 1];
|
|
274
|
+
}
|
|
275
|
+
// Validate the new character before shifting
|
|
276
|
+
if (!validKeyPattern.test(newChar)) {
|
|
277
|
+
input.value = this.inputValues[index] || '';
|
|
278
|
+
this.previousInputValues[index] = this.inputValues[index] || '';
|
|
279
|
+
this.previousInputValues = [...this.inputValues];
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
// Shift values right from the end to the insertion point
|
|
283
|
+
for (let i = this.inputValues.length - 1; i > index; i--) {
|
|
284
|
+
this.inputValues[i] = this.inputValues[i - 1];
|
|
285
|
+
if (this.inputRefs[i] != null) {
|
|
286
|
+
this.inputRefs[i].value = this.inputValues[i] || '';
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
this.inputValues[index] = newChar;
|
|
290
|
+
if (this.inputRefs[index] != null) {
|
|
291
|
+
this.inputRefs[index].value = newChar;
|
|
292
|
+
}
|
|
293
|
+
this.previousInputValues[index] = newChar;
|
|
294
|
+
this.updateValue(event);
|
|
295
|
+
this.previousInputValues = [...this.inputValues];
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
// 5. Single character replacement
|
|
299
|
+
// Handles replacing a single character in a box containing a value based
|
|
300
|
+
// on the cursor position. We need the cursor position to determine which
|
|
301
|
+
// character was the last character typed. For example, if the user types "2"
|
|
302
|
+
// in an input box with the cursor at the beginning of the value of "6",
|
|
303
|
+
// the value will be "26", but we want to grab the "2" as the last character
|
|
304
|
+
// typed.
|
|
305
|
+
const cursorPos = (_a = input.selectionStart) !== null && _a !== void 0 ? _a : value.length;
|
|
306
|
+
const newCharIndex = cursorPos - 1;
|
|
307
|
+
const newChar = (_b = value[newCharIndex]) !== null && _b !== void 0 ? _b : value[0];
|
|
308
|
+
// Check if the new character is valid before updating the value
|
|
309
|
+
if (!validKeyPattern.test(newChar)) {
|
|
310
|
+
input.value = this.inputValues[index] || '';
|
|
311
|
+
this.previousInputValues[index] = this.inputValues[index] || '';
|
|
312
|
+
this.previousInputValues = [...this.inputValues];
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
input.value = newChar;
|
|
316
|
+
this.inputValues[index] = newChar;
|
|
317
|
+
this.previousInputValues[index] = newChar;
|
|
318
|
+
this.updateValue(event);
|
|
319
|
+
this.previousInputValues = [...this.inputValues];
|
|
263
320
|
};
|
|
264
321
|
/**
|
|
265
322
|
* Handles pasting text into the input OTP component.
|
|
@@ -269,7 +326,7 @@ const InputOTP = class {
|
|
|
269
326
|
* the next empty input after pasting.
|
|
270
327
|
*/
|
|
271
328
|
this.onPaste = (event) => {
|
|
272
|
-
var _a, _b
|
|
329
|
+
var _a, _b;
|
|
273
330
|
const { inputRefs, length, validKeyPattern } = this;
|
|
274
331
|
event.preventDefault();
|
|
275
332
|
const pastedText = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text');
|
|
@@ -296,13 +353,8 @@ const InputOTP = class {
|
|
|
296
353
|
this.updateValue(event);
|
|
297
354
|
// Focus the next empty input after pasting
|
|
298
355
|
// 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
|
-
}
|
|
356
|
+
const nextEmptyIndex = validChars.length < length ? validChars.length : length - 1;
|
|
357
|
+
(_b = inputRefs[nextEmptyIndex]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
306
358
|
};
|
|
307
359
|
}
|
|
308
360
|
/**
|
|
@@ -441,6 +493,7 @@ const InputOTP = class {
|
|
|
441
493
|
});
|
|
442
494
|
// Update the value without emitting events
|
|
443
495
|
this.value = this.inputValues.join('');
|
|
496
|
+
this.previousInputValues = [...this.inputValues];
|
|
444
497
|
}
|
|
445
498
|
/**
|
|
446
499
|
* Updates the value of the input group.
|
|
@@ -562,7 +615,7 @@ const InputOTP = class {
|
|
|
562
615
|
const tabbableIndex = this.getTabbableIndex();
|
|
563
616
|
const pattern = this.getPattern();
|
|
564
617
|
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: '
|
|
618
|
+
return (h(Host, { key: '084b4f7d148a55aef6b4b51c11483ee51d70d3bd', class: createColorClasses(color, {
|
|
566
619
|
[mode]: true,
|
|
567
620
|
'has-focus': hasFocus,
|
|
568
621
|
[`input-otp-size-${size}`]: true,
|
|
@@ -570,10 +623,10 @@ const InputOTP = class {
|
|
|
570
623
|
[`input-otp-fill-${fill}`]: true,
|
|
571
624
|
'input-otp-disabled': disabled,
|
|
572
625
|
'input-otp-readonly': readonly,
|
|
573
|
-
}) }, h("div", Object.assign({ key: '
|
|
626
|
+
}) }, h("div", Object.assign({ key: '9d797deb7170bf6e4cc1acf70cca0b5d4ef51610', 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: 'a0463205729699430560032a68ade2e2ffa49b61', class: {
|
|
574
627
|
'input-otp-description': true,
|
|
575
628
|
'input-otp-description-hidden': !hasDescription,
|
|
576
|
-
} }, h("slot", { key: '
|
|
629
|
+
} }, h("slot", { key: '287fdaf0375cda3dcfafa2762d7daebf6f2bfe68' }))));
|
|
577
630
|
}
|
|
578
631
|
get el() { return getElement(this); }
|
|
579
632
|
static get watchers() { return {
|