@ionic/core 8.6.5-nightly.20250711 → 8.6.5-nightly.20250715
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 +91 -4
- package/dist/cjs/ion-input-otp.cjs.entry.js +102 -50
- package/dist/cjs/ion-modal.cjs.entry.js +91 -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 +91 -4
- package/dist/docs.json +1 -1
- package/dist/esm/ion-input-otp.entry.js +102 -50
- package/dist/esm/ion-modal.entry.js +91 -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-35c4a63f.entry.js +4 -0
- package/dist/ionic/p-ec4f95ad.entry.js +4 -0
- package/dist/types/components/input-otp/input-otp.d.ts +12 -8
- package/dist/types/components/modal/modal.d.ts +13 -0
- package/hydrate/index.js +194 -54
- package/hydrate/index.mjs +194 -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) {
|
|
@@ -1690,6 +1710,7 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
1690
1710
|
disconnectedCallback() {
|
|
1691
1711
|
this.triggerController.removeClickListener();
|
|
1692
1712
|
this.cleanupViewTransitionListener();
|
|
1713
|
+
this.cleanupParentRemovalObserver();
|
|
1693
1714
|
}
|
|
1694
1715
|
componentWillLoad() {
|
|
1695
1716
|
var _a;
|
|
@@ -1697,6 +1718,10 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
1697
1718
|
const isSheetModal = (this.isSheetModal = breakpoints !== undefined && initialBreakpoint !== undefined);
|
|
1698
1719
|
const attributesToInherit = ['aria-label', 'role'];
|
|
1699
1720
|
this.inheritedAttributes = inheritAttributes(el, attributesToInherit);
|
|
1721
|
+
// Cache original parent before modal gets moved to body during presentation
|
|
1722
|
+
if (el.parentNode) {
|
|
1723
|
+
this.cachedOriginalParent = el.parentNode;
|
|
1724
|
+
}
|
|
1700
1725
|
/**
|
|
1701
1726
|
* When using a controller modal you can set attributes
|
|
1702
1727
|
* using the htmlAttributes property. Since the above attributes
|
|
@@ -1902,6 +1927,8 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
1902
1927
|
}
|
|
1903
1928
|
// Initialize view transition listener for iOS card modals
|
|
1904
1929
|
this.initViewTransitionListener();
|
|
1930
|
+
// Initialize parent removal observer
|
|
1931
|
+
this.initParentRemovalObserver();
|
|
1905
1932
|
unlock();
|
|
1906
1933
|
}
|
|
1907
1934
|
initSwipeToClose() {
|
|
@@ -2015,6 +2042,12 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2015
2042
|
* in case the dismiss transition does run.
|
|
2016
2043
|
*/
|
|
2017
2044
|
const unlock = await this.lockController.lock();
|
|
2045
|
+
/**
|
|
2046
|
+
* Dismiss all child modals. This is especially important in
|
|
2047
|
+
* Angular and React because it's possible to lose control of a child
|
|
2048
|
+
* modal when the parent modal is dismissed.
|
|
2049
|
+
*/
|
|
2050
|
+
await this.dismissNestedModals();
|
|
2018
2051
|
/**
|
|
2019
2052
|
* If a canDismiss handler is responsible
|
|
2020
2053
|
* for calling the dismiss method, we should
|
|
@@ -2056,6 +2089,7 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2056
2089
|
this.gesture.destroy();
|
|
2057
2090
|
}
|
|
2058
2091
|
this.cleanupViewTransitionListener();
|
|
2092
|
+
this.cleanupParentRemovalObserver();
|
|
2059
2093
|
}
|
|
2060
2094
|
this.currentBreakpoint = undefined;
|
|
2061
2095
|
this.animation = undefined;
|
|
@@ -2233,6 +2267,59 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2233
2267
|
}
|
|
2234
2268
|
}
|
|
2235
2269
|
}
|
|
2270
|
+
async dismissNestedModals() {
|
|
2271
|
+
const nestedModals = document.querySelectorAll(`ion-modal[data-parent-ion-modal="${this.el.id}"]`);
|
|
2272
|
+
nestedModals === null || nestedModals === void 0 ? void 0 : nestedModals.forEach(async (modal) => {
|
|
2273
|
+
await modal.dismiss(undefined, 'parent-dismissed');
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
2276
|
+
initParentRemovalObserver() {
|
|
2277
|
+
if (typeof MutationObserver === 'undefined') {
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
// Only observe if we have a cached parent and are in browser environment
|
|
2281
|
+
if (typeof window === 'undefined' || !this.cachedOriginalParent) {
|
|
2282
|
+
return;
|
|
2283
|
+
}
|
|
2284
|
+
// Don't observe document or fragment nodes as they can't be "removed"
|
|
2285
|
+
if (this.cachedOriginalParent.nodeType === Node.DOCUMENT_NODE ||
|
|
2286
|
+
this.cachedOriginalParent.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
|
|
2287
|
+
return;
|
|
2288
|
+
}
|
|
2289
|
+
this.parentRemovalObserver = new MutationObserver((mutations) => {
|
|
2290
|
+
mutations.forEach((mutation) => {
|
|
2291
|
+
if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
|
|
2292
|
+
// Check if our cached original parent was removed
|
|
2293
|
+
const cachedParentWasRemoved = Array.from(mutation.removedNodes).some((node) => {
|
|
2294
|
+
var _a, _b;
|
|
2295
|
+
const isDirectMatch = node === this.cachedOriginalParent;
|
|
2296
|
+
const isContainedMatch = this.cachedOriginalParent
|
|
2297
|
+
? (_b = (_a = node).contains) === null || _b === void 0 ? void 0 : _b.call(_a, this.cachedOriginalParent)
|
|
2298
|
+
: false;
|
|
2299
|
+
return isDirectMatch || isContainedMatch;
|
|
2300
|
+
});
|
|
2301
|
+
// Also check if parent is no longer connected to DOM
|
|
2302
|
+
const cachedParentDisconnected = this.cachedOriginalParent && !this.cachedOriginalParent.isConnected;
|
|
2303
|
+
if (cachedParentWasRemoved || cachedParentDisconnected) {
|
|
2304
|
+
this.dismiss(undefined, 'parent-removed');
|
|
2305
|
+
// Release the reference to the cached original parent
|
|
2306
|
+
// so we don't have a memory leak
|
|
2307
|
+
this.cachedOriginalParent = undefined;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
});
|
|
2311
|
+
});
|
|
2312
|
+
// Observe document body with subtree to catch removals at any level
|
|
2313
|
+
this.parentRemovalObserver.observe(document.body, {
|
|
2314
|
+
childList: true,
|
|
2315
|
+
subtree: true,
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
cleanupParentRemovalObserver() {
|
|
2319
|
+
var _a;
|
|
2320
|
+
(_a = this.parentRemovalObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
2321
|
+
this.parentRemovalObserver = undefined;
|
|
2322
|
+
}
|
|
2236
2323
|
render() {
|
|
2237
2324
|
const { handle, isSheetModal, presentingElement, htmlAttributes, handleBehavior, inheritedAttributes, focusTrap, expandToScroll, } = this;
|
|
2238
2325
|
const showHandle = handle !== false && isSheetModal;
|
|
@@ -2240,20 +2327,20 @@ const Modal = /*@__PURE__*/ proxyCustomElement(class Modal extends HTMLElement {
|
|
|
2240
2327
|
const isCardModal = presentingElement !== undefined && mode === 'ios';
|
|
2241
2328
|
const isHandleCycle = handleBehavior === 'cycle';
|
|
2242
2329
|
const isSheetModalWithHandle = isSheetModal && showHandle;
|
|
2243
|
-
return (h(Host, Object.assign({ key: '
|
|
2330
|
+
return (h(Host, Object.assign({ key: 'f274213df23995f7d3495648c828dbab6d040a53', "no-router": true,
|
|
2244
2331
|
// Allow the modal to be navigable when the handle is focusable
|
|
2245
2332
|
tabIndex: isHandleCycle && isSheetModalWithHandle ? 0 : -1 }, htmlAttributes, { style: {
|
|
2246
2333
|
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: '
|
|
2334
|
+
}, 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: '8893496a8ce7364b3695a628eb4e6bd8c275f78e', ref: (el) => (this.backdropEl = el), visible: this.showBackdrop, tappable: this.backdropDismiss, part: "backdrop" }), mode === 'ios' && h("div", { key: '66ceabd642566503f2c75e84ef7f643d73a97428', class: "modal-shadow" }), h("div", Object.assign({ key: '4cb89ce5b84219271d0d203babf066348a4c5433',
|
|
2248
2335
|
/*
|
|
2249
2336
|
role and aria-modal must be used on the
|
|
2250
2337
|
same element. They must also be set inside the
|
|
2251
2338
|
shadow DOM otherwise ion-button will not be highlighted
|
|
2252
2339
|
when using VoiceOver: https://bugs.webkit.org/show_bug.cgi?id=247134
|
|
2253
2340
|
*/
|
|
2254
|
-
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '
|
|
2341
|
+
role: "dialog" }, inheritedAttributes, { "aria-modal": "true", class: "modal-wrapper ion-overlay-wrapper", part: "content", ref: (el) => (this.wrapperEl = el) }), showHandle && (h("button", { key: '9062f7bcd45203642ce7ca0521b2bb0bf1f77192', class: "modal-handle",
|
|
2255
2342
|
// 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: '
|
|
2343
|
+
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: '34a29599e80254186c6d7d77b3161abcda76eab7', onSlotchange: this.onSlotChange }))));
|
|
2257
2344
|
}
|
|
2258
2345
|
get el() { return this; }
|
|
2259
2346
|
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 {
|