input-shims.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. /*!
  2. * (C) Ionic http://ionicframework.com - MIT License
  3. */
  4. import { w as win, d as doc } from './index6.js';
  5. import { g as getScrollElement, c as scrollByPoint, a as findClosestIonContent } from './index8.js';
  6. import { a as addEventListener, b as removeEventListener, r as raf, c as componentOnReady } from './helpers.js';
  7. import { a as KeyboardResize, K as Keyboard } from './keyboard.js';
  8. const cloneMap = new WeakMap();
  9. const relocateInput = (componentEl, inputEl, shouldRelocate, inputRelativeY = 0, disabledClonedInput = false) => {
  10. if (cloneMap.has(componentEl) === shouldRelocate) {
  11. return;
  12. }
  13. if (shouldRelocate) {
  14. addClone(componentEl, inputEl, inputRelativeY, disabledClonedInput);
  15. }
  16. else {
  17. removeClone(componentEl, inputEl);
  18. }
  19. };
  20. const isFocused = (input) => {
  21. /**
  22. * https://developer.mozilla.org/en-US/docs/Web/API/Node/getRootNode
  23. * Calling getRootNode on an element in standard web page will return HTMLDocument.
  24. * Calling getRootNode on an element inside of the Shadow DOM will return the associated ShadowRoot.
  25. * Calling getRootNode on an element that is not attached to a document/shadow tree will return
  26. * the root of the DOM tree it belongs to.
  27. * isFocused is used for the hide-caret utility which only considers input/textarea elements
  28. * that are present in the DOM, so we don't set types for that final case since it does not apply.
  29. */
  30. return input === input.getRootNode().activeElement;
  31. };
  32. const addClone = (componentEl, inputEl, inputRelativeY, disabledClonedInput = false) => {
  33. // this allows for the actual input to receive the focus from
  34. // the user's touch event, but before it receives focus, it
  35. // moves the actual input to a location that will not screw
  36. // up the app's layout, and does not allow the native browser
  37. // to attempt to scroll the input into place (messing up headers/footers)
  38. // the cloned input fills the area of where native input should be
  39. // while the native input fakes out the browser by relocating itself
  40. // before it receives the actual focus event
  41. // We hide the focused input (with the visible caret) invisible by making it scale(0),
  42. const parentEl = inputEl.parentNode;
  43. // DOM WRITES
  44. const clonedEl = inputEl.cloneNode(false);
  45. clonedEl.classList.add('cloned-input');
  46. clonedEl.tabIndex = -1;
  47. /**
  48. * Making the cloned input disabled prevents
  49. * Chrome for Android from still scrolling
  50. * the entire page since this cloned input
  51. * will briefly be hidden by the keyboard
  52. * even though it is not focused.
  53. *
  54. * This is not needed on iOS. While this
  55. * does not cause functional issues on iOS,
  56. * the input still appears slightly dimmed even
  57. * if we set opacity: 1.
  58. */
  59. if (disabledClonedInput) {
  60. clonedEl.disabled = true;
  61. }
  62. parentEl.appendChild(clonedEl);
  63. cloneMap.set(componentEl, clonedEl);
  64. const doc = componentEl.ownerDocument;
  65. const tx = doc.dir === 'rtl' ? 9999 : -9999;
  66. componentEl.style.pointerEvents = 'none';
  67. inputEl.style.transform = `translate3d(${tx}px,${inputRelativeY}px,0) scale(0)`;
  68. };
  69. const removeClone = (componentEl, inputEl) => {
  70. const clone = cloneMap.get(componentEl);
  71. if (clone) {
  72. cloneMap.delete(componentEl);
  73. clone.remove();
  74. }
  75. componentEl.style.pointerEvents = '';
  76. inputEl.style.transform = '';
  77. };
  78. /**
  79. * Factoring in 50px gives us some room
  80. * in case the keyboard shows password/autofill bars
  81. * asynchronously.
  82. */
  83. const SCROLL_AMOUNT_PADDING = 50;
  84. const enableHideCaretOnScroll = (componentEl, inputEl, scrollEl) => {
  85. if (!scrollEl || !inputEl) {
  86. return () => {
  87. return;
  88. };
  89. }
  90. const scrollHideCaret = (shouldHideCaret) => {
  91. if (isFocused(inputEl)) {
  92. relocateInput(componentEl, inputEl, shouldHideCaret);
  93. }
  94. };
  95. const onBlur = () => relocateInput(componentEl, inputEl, false);
  96. const hideCaret = () => scrollHideCaret(true);
  97. const showCaret = () => scrollHideCaret(false);
  98. addEventListener(scrollEl, 'ionScrollStart', hideCaret);
  99. addEventListener(scrollEl, 'ionScrollEnd', showCaret);
  100. inputEl.addEventListener('blur', onBlur);
  101. return () => {
  102. removeEventListener(scrollEl, 'ionScrollStart', hideCaret);
  103. removeEventListener(scrollEl, 'ionScrollEnd', showCaret);
  104. inputEl.removeEventListener('blur', onBlur);
  105. };
  106. };
  107. const SKIP_SELECTOR = 'input, textarea, [no-blur], [contenteditable]';
  108. const enableInputBlurring = () => {
  109. let focused = true;
  110. let didScroll = false;
  111. const doc = document;
  112. const onScroll = () => {
  113. didScroll = true;
  114. };
  115. const onFocusin = () => {
  116. focused = true;
  117. };
  118. const onTouchend = (ev) => {
  119. // if app did scroll return early
  120. if (didScroll) {
  121. didScroll = false;
  122. return;
  123. }
  124. const active = doc.activeElement;
  125. if (!active) {
  126. return;
  127. }
  128. // only blur if the active element is a text-input or a textarea
  129. if (active.matches(SKIP_SELECTOR)) {
  130. return;
  131. }
  132. // if the selected target is the active element, do not blur
  133. const tapped = ev.target;
  134. if (tapped === active) {
  135. return;
  136. }
  137. if (tapped.matches(SKIP_SELECTOR) || tapped.closest(SKIP_SELECTOR)) {
  138. return;
  139. }
  140. focused = false;
  141. // TODO FW-2796: find a better way, why 50ms?
  142. setTimeout(() => {
  143. if (!focused) {
  144. active.blur();
  145. }
  146. }, 50);
  147. };
  148. addEventListener(doc, 'ionScrollStart', onScroll);
  149. doc.addEventListener('focusin', onFocusin, true);
  150. doc.addEventListener('touchend', onTouchend, false);
  151. return () => {
  152. removeEventListener(doc, 'ionScrollStart', onScroll, true);
  153. doc.removeEventListener('focusin', onFocusin, true);
  154. doc.removeEventListener('touchend', onTouchend, false);
  155. };
  156. };
  157. const SCROLL_ASSIST_SPEED = 0.3;
  158. const getScrollData = (componentEl, contentEl, keyboardHeight, platformHeight) => {
  159. var _a;
  160. const itemEl = (_a = componentEl.closest('ion-item,[ion-item]')) !== null && _a !== void 0 ? _a : componentEl;
  161. return calcScrollData(itemEl.getBoundingClientRect(), contentEl.getBoundingClientRect(), keyboardHeight, platformHeight);
  162. };
  163. const calcScrollData = (inputRect, contentRect, keyboardHeight, platformHeight) => {
  164. // compute input's Y values relative to the body
  165. const inputTop = inputRect.top;
  166. const inputBottom = inputRect.bottom;
  167. // compute visible area
  168. const visibleAreaTop = contentRect.top;
  169. const visibleAreaBottom = Math.min(contentRect.bottom, platformHeight - keyboardHeight);
  170. // compute safe area
  171. const safeAreaTop = visibleAreaTop + 15;
  172. const safeAreaBottom = visibleAreaBottom - SCROLL_AMOUNT_PADDING;
  173. // figure out if each edge of the input is within the safe area
  174. const distanceToBottom = safeAreaBottom - inputBottom;
  175. const distanceToTop = safeAreaTop - inputTop;
  176. // desiredScrollAmount is the negated distance to the safe area according to our calculations.
  177. const desiredScrollAmount = Math.round(distanceToBottom < 0 ? -distanceToBottom : distanceToTop > 0 ? -distanceToTop : 0);
  178. // our calculations make some assumptions that aren't always true, like the keyboard being closed when an input
  179. // gets focus, so make sure we don't scroll the input above the visible area
  180. const scrollAmount = Math.min(desiredScrollAmount, inputTop - visibleAreaTop);
  181. const distance = Math.abs(scrollAmount);
  182. const duration = distance / SCROLL_ASSIST_SPEED;
  183. const scrollDuration = Math.min(400, Math.max(150, duration));
  184. return {
  185. scrollAmount,
  186. scrollDuration,
  187. scrollPadding: keyboardHeight,
  188. inputSafeY: -(inputTop - safeAreaTop) + 4,
  189. };
  190. };
  191. const PADDING_TIMER_KEY = '$ionPaddingTimer';
  192. /**
  193. * Scroll padding adds additional padding to the bottom
  194. * of ion-content so that there is enough scroll space
  195. * for an input to be scrolled above the keyboard. This
  196. * is needed in environments where the webview does not
  197. * resize when the keyboard opens.
  198. *
  199. * Example: If an input at the bottom of ion-content is
  200. * focused, there is no additional scrolling space below
  201. * it, so the input cannot be scrolled above the keyboard.
  202. * Scroll padding fixes this by adding padding equal to the
  203. * height of the keyboard to the bottom of the content.
  204. *
  205. * Common environments where this is needed:
  206. * - Mobile Safari: The keyboard overlays the content
  207. * - Capacitor/Cordova on iOS: The keyboard overlays the content
  208. * when the KeyboardResize mode is set to 'none'.
  209. */
  210. const setScrollPadding = (contentEl, paddingAmount, clearCallback) => {
  211. const timer = contentEl[PADDING_TIMER_KEY];
  212. if (timer) {
  213. clearTimeout(timer);
  214. }
  215. if (paddingAmount > 0) {
  216. contentEl.style.setProperty('--keyboard-offset', `${paddingAmount}px`);
  217. }
  218. else {
  219. contentEl[PADDING_TIMER_KEY] = setTimeout(() => {
  220. contentEl.style.setProperty('--keyboard-offset', '0px');
  221. if (clearCallback) {
  222. clearCallback();
  223. }
  224. }, 120);
  225. }
  226. };
  227. /**
  228. * When an input is about to be focused,
  229. * set a timeout to clear any scroll padding
  230. * on the content. Note: The clearing
  231. * is done on a timeout so that if users
  232. * are moving focus from one input to the next
  233. * then re-adding scroll padding to the new
  234. * input with cancel the timeout to clear the
  235. * scroll padding.
  236. */
  237. const setClearScrollPaddingListener = (inputEl, contentEl, doneCallback) => {
  238. const clearScrollPadding = () => {
  239. if (contentEl) {
  240. setScrollPadding(contentEl, 0, doneCallback);
  241. }
  242. };
  243. inputEl.addEventListener('focusout', clearScrollPadding, { once: true });
  244. };
  245. let currentPadding = 0;
  246. const SKIP_SCROLL_ASSIST = 'data-ionic-skip-scroll-assist';
  247. const enableScrollAssist = (componentEl, inputEl, contentEl, footerEl, keyboardHeight, enableScrollPadding, keyboardResize, disableClonedInput = false) => {
  248. /**
  249. * Scroll padding should only be added if:
  250. * 1. The global scrollPadding config option
  251. * is set to true.
  252. * 2. The native keyboard resize mode is either "none"
  253. * (keyboard overlays webview) or undefined (resize
  254. * information unavailable)
  255. * Resize info is available on Capacitor 4+
  256. */
  257. const addScrollPadding = enableScrollPadding && (keyboardResize === undefined || keyboardResize.mode === KeyboardResize.None);
  258. /**
  259. * This tracks whether or not the keyboard has been
  260. * presented for a single focused text field. Note
  261. * that it does not track if the keyboard is open
  262. * in general such as if the keyboard is open for
  263. * a different focused text field.
  264. */
  265. let hasKeyboardBeenPresentedForTextField = false;
  266. /**
  267. * When adding scroll padding we need to know
  268. * how much of the viewport the keyboard obscures.
  269. * We do this by subtracting the keyboard height
  270. * from the platform height.
  271. *
  272. * If we compute this value when switching between
  273. * inputs then the webview may already be resized.
  274. * At this point, `win.innerHeight` has already accounted
  275. * for the keyboard meaning we would then subtract
  276. * the keyboard height again. This will result in the input
  277. * being scrolled more than it needs to.
  278. */
  279. const platformHeight = win !== undefined ? win.innerHeight : 0;
  280. /**
  281. * Scroll assist is run when a text field
  282. * is focused. However, it may need to
  283. * re-run when the keyboard size changes
  284. * such that the text field is now hidden
  285. * underneath the keyboard.
  286. * This function re-runs scroll assist
  287. * when that happens.
  288. *
  289. * One limitation of this is on a web browser
  290. * where native keyboard APIs do not have cross-browser
  291. * support. `ionKeyboardDidShow` relies on the Visual Viewport API.
  292. * This means that if the keyboard changes but does not change
  293. * geometry, then scroll assist will not re-run even if
  294. * the user has scrolled the text field under the keyboard.
  295. * This is not a problem when running in Cordova/Capacitor
  296. * because `ionKeyboardDidShow` uses the native events
  297. * which fire every time the keyboard changes.
  298. */
  299. const keyboardShow = (ev) => {
  300. /**
  301. * If the keyboard has not yet been presented
  302. * for this text field then the text field has just
  303. * received focus. In that case, the focusin listener
  304. * will run scroll assist.
  305. */
  306. if (hasKeyboardBeenPresentedForTextField === false) {
  307. hasKeyboardBeenPresentedForTextField = true;
  308. return;
  309. }
  310. /**
  311. * Otherwise, the keyboard has already been presented
  312. * for the focused text field.
  313. * This means that the keyboard likely changed
  314. * geometry, and we need to re-run scroll assist.
  315. * This can happen when the user rotates their device
  316. * or when they switch keyboards.
  317. *
  318. * Make sure we pass in the computed keyboard height
  319. * rather than the estimated keyboard height.
  320. *
  321. * Since the keyboard is already open then we do not
  322. * need to wait for the webview to resize, so we pass
  323. * "waitForResize: false".
  324. */
  325. jsSetFocus(componentEl, inputEl, contentEl, footerEl, ev.detail.keyboardHeight, addScrollPadding, disableClonedInput, platformHeight, false);
  326. };
  327. /**
  328. * Reset the internal state when the text field loses focus.
  329. */
  330. const focusOut = () => {
  331. hasKeyboardBeenPresentedForTextField = false;
  332. win === null || win === void 0 ? void 0 : win.removeEventListener('ionKeyboardDidShow', keyboardShow);
  333. componentEl.removeEventListener('focusout', focusOut);
  334. };
  335. /**
  336. * When the input is about to receive
  337. * focus, we need to move it to prevent
  338. * mobile Safari from adjusting the viewport.
  339. */
  340. const focusIn = async () => {
  341. /**
  342. * Scroll assist should not run again
  343. * on inputs that have been manually
  344. * focused inside of the scroll assist
  345. * implementation.
  346. */
  347. if (inputEl.hasAttribute(SKIP_SCROLL_ASSIST)) {
  348. inputEl.removeAttribute(SKIP_SCROLL_ASSIST);
  349. return;
  350. }
  351. jsSetFocus(componentEl, inputEl, contentEl, footerEl, keyboardHeight, addScrollPadding, disableClonedInput, platformHeight);
  352. win === null || win === void 0 ? void 0 : win.addEventListener('ionKeyboardDidShow', keyboardShow);
  353. componentEl.addEventListener('focusout', focusOut);
  354. };
  355. componentEl.addEventListener('focusin', focusIn);
  356. return () => {
  357. componentEl.removeEventListener('focusin', focusIn);
  358. win === null || win === void 0 ? void 0 : win.removeEventListener('ionKeyboardDidShow', keyboardShow);
  359. componentEl.removeEventListener('focusout', focusOut);
  360. };
  361. };
  362. /**
  363. * Use this function when you want to manually
  364. * focus an input but not have scroll assist run again.
  365. */
  366. const setManualFocus = (el) => {
  367. /**
  368. * If element is already focused then
  369. * a new focusin event will not be dispatched
  370. * to remove the SKIL_SCROLL_ASSIST attribute.
  371. */
  372. if (document.activeElement === el) {
  373. return;
  374. }
  375. el.setAttribute(SKIP_SCROLL_ASSIST, 'true');
  376. el.focus();
  377. };
  378. const jsSetFocus = async (componentEl, inputEl, contentEl, footerEl, keyboardHeight, enableScrollPadding, disableClonedInput = false, platformHeight = 0, waitForResize = true) => {
  379. if (!contentEl && !footerEl) {
  380. return;
  381. }
  382. const scrollData = getScrollData(componentEl, (contentEl || footerEl), keyboardHeight, platformHeight);
  383. if (contentEl && Math.abs(scrollData.scrollAmount) < 4) {
  384. // the text input is in a safe position that doesn't
  385. // require it to be scrolled into view, just set focus now
  386. setManualFocus(inputEl);
  387. /**
  388. * Even though the input does not need
  389. * scroll assist, we should preserve the
  390. * the scroll padding as users could be moving
  391. * focus from an input that needs scroll padding
  392. * to an input that does not need scroll padding.
  393. * If we remove the scroll padding now, users will
  394. * see the page jump.
  395. */
  396. if (enableScrollPadding && contentEl !== null) {
  397. setScrollPadding(contentEl, currentPadding);
  398. setClearScrollPaddingListener(inputEl, contentEl, () => (currentPadding = 0));
  399. }
  400. return;
  401. }
  402. // temporarily move the focus to the focus holder so the browser
  403. // doesn't freak out while it's trying to get the input in place
  404. // at this point the native text input still does not have focus
  405. relocateInput(componentEl, inputEl, true, scrollData.inputSafeY, disableClonedInput);
  406. setManualFocus(inputEl);
  407. /**
  408. * Relocating/Focusing input causes the
  409. * click event to be cancelled, so
  410. * manually fire one here.
  411. */
  412. raf(() => componentEl.click());
  413. /**
  414. * If enabled, we can add scroll padding to
  415. * the bottom of the content so that scroll assist
  416. * has enough room to scroll the input above
  417. * the keyboard.
  418. */
  419. if (enableScrollPadding && contentEl) {
  420. currentPadding = scrollData.scrollPadding;
  421. setScrollPadding(contentEl, currentPadding);
  422. }
  423. if (typeof window !== 'undefined') {
  424. let scrollContentTimeout;
  425. const scrollContent = async () => {
  426. // clean up listeners and timeouts
  427. if (scrollContentTimeout !== undefined) {
  428. clearTimeout(scrollContentTimeout);
  429. }
  430. window.removeEventListener('ionKeyboardDidShow', doubleKeyboardEventListener);
  431. window.removeEventListener('ionKeyboardDidShow', scrollContent);
  432. // scroll the input into place
  433. if (contentEl) {
  434. await scrollByPoint(contentEl, 0, scrollData.scrollAmount, scrollData.scrollDuration);
  435. }
  436. // the scroll view is in the correct position now
  437. // give the native text input focus
  438. relocateInput(componentEl, inputEl, false, scrollData.inputSafeY);
  439. // ensure this is the focused input
  440. setManualFocus(inputEl);
  441. /**
  442. * When the input is about to be blurred
  443. * we should set a timeout to remove
  444. * any scroll padding.
  445. */
  446. if (enableScrollPadding) {
  447. setClearScrollPaddingListener(inputEl, contentEl, () => (currentPadding = 0));
  448. }
  449. };
  450. const doubleKeyboardEventListener = () => {
  451. window.removeEventListener('ionKeyboardDidShow', doubleKeyboardEventListener);
  452. window.addEventListener('ionKeyboardDidShow', scrollContent);
  453. };
  454. if (contentEl) {
  455. const scrollEl = await getScrollElement(contentEl);
  456. /**
  457. * scrollData will only consider the amount we need
  458. * to scroll in order to properly bring the input
  459. * into view. It will not consider the amount
  460. * we can scroll in the content element.
  461. * As a result, scrollData may request a greater
  462. * scroll position than is currently available
  463. * in the DOM. If this is the case, we need to
  464. * wait for the webview to resize/the keyboard
  465. * to show in order for additional scroll
  466. * bandwidth to become available.
  467. */
  468. const totalScrollAmount = scrollEl.scrollHeight - scrollEl.clientHeight;
  469. if (waitForResize && scrollData.scrollAmount > totalScrollAmount - scrollEl.scrollTop) {
  470. /**
  471. * On iOS devices, the system will show a "Passwords" bar above the keyboard
  472. * after the initial keyboard is shown. This prevents the webview from resizing
  473. * until the "Passwords" bar is shown, so we need to wait for that to happen first.
  474. */
  475. if (inputEl.type === 'password') {
  476. // Add 50px to account for the "Passwords" bar
  477. scrollData.scrollAmount += SCROLL_AMOUNT_PADDING;
  478. window.addEventListener('ionKeyboardDidShow', doubleKeyboardEventListener);
  479. }
  480. else {
  481. window.addEventListener('ionKeyboardDidShow', scrollContent);
  482. }
  483. /**
  484. * This should only fire in 2 instances:
  485. * 1. The app is very slow.
  486. * 2. The app is running in a browser on an old OS
  487. * that does not support Ionic Keyboard Events
  488. */
  489. scrollContentTimeout = setTimeout(scrollContent, 1000);
  490. return;
  491. }
  492. }
  493. scrollContent();
  494. }
  495. };
  496. const INPUT_BLURRING = true;
  497. const startInputShims = async (config, platform) => {
  498. /**
  499. * If doc is undefined then we are in an SSR environment
  500. * where input shims do not apply.
  501. */
  502. if (doc === undefined) {
  503. return;
  504. }
  505. const isIOS = platform === 'ios';
  506. const isAndroid = platform === 'android';
  507. /**
  508. * Hide Caret and Input Blurring are needed on iOS.
  509. * Scroll Assist and Scroll Padding are needed on iOS and Android
  510. * with Chrome web browser (not Chrome webview).
  511. */
  512. const keyboardHeight = config.getNumber('keyboardHeight', 290);
  513. const scrollAssist = config.getBoolean('scrollAssist', true);
  514. const hideCaret = config.getBoolean('hideCaretOnScroll', isIOS);
  515. /**
  516. * The team is evaluating if inputBlurring is still needed. As a result
  517. * this feature is disabled by default as of Ionic 8.0. Developers are
  518. * able to re-enable it temporarily. The team may remove this utility
  519. * if it is determined that doing so would not bring any adverse side effects.
  520. * TODO FW-6014 remove input blurring utility (including implementation)
  521. */
  522. const inputBlurring = config.getBoolean('inputBlurring', false);
  523. const scrollPadding = config.getBoolean('scrollPadding', true);
  524. const inputs = Array.from(doc.querySelectorAll('ion-input, ion-textarea'));
  525. const hideCaretMap = new WeakMap();
  526. const scrollAssistMap = new WeakMap();
  527. /**
  528. * Grab the native keyboard resize configuration
  529. * and pass it to scroll assist. Scroll assist requires
  530. * that we adjust the input right before the input
  531. * is about to be focused. If we called `Keyboard.getResizeMode`
  532. * on focusin in scroll assist, we could potentially adjust the
  533. * input too late since this call is async.
  534. */
  535. const keyboardResizeMode = await Keyboard.getResizeMode();
  536. const registerInput = async (componentEl) => {
  537. await new Promise((resolve) => componentOnReady(componentEl, resolve));
  538. const inputRoot = componentEl.shadowRoot || componentEl;
  539. const inputEl = inputRoot.querySelector('input') || inputRoot.querySelector('textarea');
  540. const scrollEl = findClosestIonContent(componentEl);
  541. const footerEl = !scrollEl ? componentEl.closest('ion-footer') : null;
  542. if (!inputEl) {
  543. return;
  544. }
  545. if (!!scrollEl && hideCaret && !hideCaretMap.has(componentEl)) {
  546. const rmFn = enableHideCaretOnScroll(componentEl, inputEl, scrollEl);
  547. hideCaretMap.set(componentEl, rmFn);
  548. }
  549. /**
  550. * date/datetime-locale inputs on mobile devices show date picker
  551. * overlays instead of keyboards. As a result, scroll assist is
  552. * not needed. This also works around a bug in iOS <16 where
  553. * scroll assist causes the browser to lock up. See FW-1997.
  554. */
  555. const isDateInput = inputEl.type === 'date' || inputEl.type === 'datetime-local';
  556. if (!isDateInput &&
  557. (!!scrollEl || !!footerEl) &&
  558. scrollAssist &&
  559. !scrollAssistMap.has(componentEl)) {
  560. const rmFn = enableScrollAssist(componentEl, inputEl, scrollEl, footerEl, keyboardHeight, scrollPadding, keyboardResizeMode, isAndroid);
  561. scrollAssistMap.set(componentEl, rmFn);
  562. }
  563. };
  564. const unregisterInput = (componentEl) => {
  565. if (hideCaret) {
  566. const fn = hideCaretMap.get(componentEl);
  567. if (fn) {
  568. fn();
  569. }
  570. hideCaretMap.delete(componentEl);
  571. }
  572. if (scrollAssist) {
  573. const fn = scrollAssistMap.get(componentEl);
  574. if (fn) {
  575. fn();
  576. }
  577. scrollAssistMap.delete(componentEl);
  578. }
  579. };
  580. if (inputBlurring && INPUT_BLURRING) {
  581. enableInputBlurring();
  582. }
  583. // Input might be already loaded in the DOM before ion-device-hacks did.
  584. // At this point we need to look for all of the inputs not registered yet
  585. // and register them.
  586. for (const input of inputs) {
  587. registerInput(input);
  588. }
  589. doc.addEventListener('ionInputDidLoad', (ev) => {
  590. registerInput(ev.detail);
  591. });
  592. doc.addEventListener('ionInputDidUnload', (ev) => {
  593. unregisterInput(ev.detail);
  594. });
  595. };
  596. export { startInputShims };