helpers-8a48fdea.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /*!
  2. * (C) Ionic http://ionicframework.com - MIT License
  3. */
  4. 'use strict';
  5. const index = require('./index-cc858e97.js');
  6. const transitionEndAsync = (el, expectedDuration = 0) => {
  7. return new Promise((resolve) => {
  8. transitionEnd(el, expectedDuration, resolve);
  9. });
  10. };
  11. /**
  12. * Allows developer to wait for a transition
  13. * to finish and fallback to a timer if the
  14. * transition is cancelled or otherwise
  15. * never finishes. Also see transitionEndAsync
  16. * which is an await-able version of this.
  17. */
  18. const transitionEnd = (el, expectedDuration = 0, callback) => {
  19. let unRegTrans;
  20. let animationTimeout;
  21. const opts = { passive: true };
  22. const ANIMATION_FALLBACK_TIMEOUT = 500;
  23. const unregister = () => {
  24. if (unRegTrans) {
  25. unRegTrans();
  26. }
  27. };
  28. const onTransitionEnd = (ev) => {
  29. if (ev === undefined || el === ev.target) {
  30. unregister();
  31. callback(ev);
  32. }
  33. };
  34. if (el) {
  35. el.addEventListener('webkitTransitionEnd', onTransitionEnd, opts);
  36. el.addEventListener('transitionend', onTransitionEnd, opts);
  37. animationTimeout = setTimeout(onTransitionEnd, expectedDuration + ANIMATION_FALLBACK_TIMEOUT);
  38. unRegTrans = () => {
  39. if (animationTimeout !== undefined) {
  40. clearTimeout(animationTimeout);
  41. animationTimeout = undefined;
  42. }
  43. el.removeEventListener('webkitTransitionEnd', onTransitionEnd, opts);
  44. el.removeEventListener('transitionend', onTransitionEnd, opts);
  45. };
  46. }
  47. return unregister;
  48. };
  49. /**
  50. * Waits for a component to be ready for
  51. * both custom element and non-custom element builds.
  52. * If non-custom element build, el.componentOnReady
  53. * will be used.
  54. * For custom element builds, we wait a frame
  55. * so that the inner contents of the component
  56. * have a chance to render.
  57. *
  58. * Use this utility rather than calling
  59. * el.componentOnReady yourself.
  60. */
  61. const componentOnReady = (el, callback) => {
  62. if (el.componentOnReady) {
  63. // eslint-disable-next-line custom-rules/no-component-on-ready-method
  64. el.componentOnReady().then((resolvedEl) => callback(resolvedEl));
  65. }
  66. else {
  67. raf(() => callback(el));
  68. }
  69. };
  70. /**
  71. * This functions checks if a Stencil component is using
  72. * the lazy loaded build of Stencil. Returns `true` if
  73. * the component is lazy loaded. Returns `false` otherwise.
  74. */
  75. const hasLazyBuild = (stencilEl) => {
  76. return stencilEl.componentOnReady !== undefined;
  77. };
  78. /**
  79. * Elements inside of web components sometimes need to inherit global attributes
  80. * set on the host. For example, the inner input in `ion-input` should inherit
  81. * the `title` attribute that developers set directly on `ion-input`. This
  82. * helper function should be called in componentWillLoad and assigned to a variable
  83. * that is later used in the render function.
  84. *
  85. * This does not need to be reactive as changing attributes on the host element
  86. * does not trigger a re-render.
  87. */
  88. const inheritAttributes = (el, attributes = []) => {
  89. const attributeObject = {};
  90. attributes.forEach((attr) => {
  91. if (el.hasAttribute(attr)) {
  92. const value = el.getAttribute(attr);
  93. if (value !== null) {
  94. attributeObject[attr] = el.getAttribute(attr);
  95. }
  96. el.removeAttribute(attr);
  97. }
  98. });
  99. return attributeObject;
  100. };
  101. /**
  102. * List of available ARIA attributes + `role`.
  103. * Removed deprecated attributes.
  104. * https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes
  105. */
  106. const ariaAttributes = [
  107. 'role',
  108. 'aria-activedescendant',
  109. 'aria-atomic',
  110. 'aria-autocomplete',
  111. 'aria-braillelabel',
  112. 'aria-brailleroledescription',
  113. 'aria-busy',
  114. 'aria-checked',
  115. 'aria-colcount',
  116. 'aria-colindex',
  117. 'aria-colindextext',
  118. 'aria-colspan',
  119. 'aria-controls',
  120. 'aria-current',
  121. 'aria-describedby',
  122. 'aria-description',
  123. 'aria-details',
  124. 'aria-disabled',
  125. 'aria-errormessage',
  126. 'aria-expanded',
  127. 'aria-flowto',
  128. 'aria-haspopup',
  129. 'aria-hidden',
  130. 'aria-invalid',
  131. 'aria-keyshortcuts',
  132. 'aria-label',
  133. 'aria-labelledby',
  134. 'aria-level',
  135. 'aria-live',
  136. 'aria-multiline',
  137. 'aria-multiselectable',
  138. 'aria-orientation',
  139. 'aria-owns',
  140. 'aria-placeholder',
  141. 'aria-posinset',
  142. 'aria-pressed',
  143. 'aria-readonly',
  144. 'aria-relevant',
  145. 'aria-required',
  146. 'aria-roledescription',
  147. 'aria-rowcount',
  148. 'aria-rowindex',
  149. 'aria-rowindextext',
  150. 'aria-rowspan',
  151. 'aria-selected',
  152. 'aria-setsize',
  153. 'aria-sort',
  154. 'aria-valuemax',
  155. 'aria-valuemin',
  156. 'aria-valuenow',
  157. 'aria-valuetext',
  158. ];
  159. /**
  160. * Returns an array of aria attributes that should be copied from
  161. * the shadow host element to a target within the light DOM.
  162. * @param el The element that the attributes should be copied from.
  163. * @param ignoreList The list of aria-attributes to ignore reflecting and removing from the host.
  164. * Use this in instances where we manually specify aria attributes on the `<Host>` element.
  165. */
  166. const inheritAriaAttributes = (el, ignoreList) => {
  167. let attributesToInherit = ariaAttributes;
  168. if (ignoreList && ignoreList.length > 0) {
  169. attributesToInherit = attributesToInherit.filter((attr) => !ignoreList.includes(attr));
  170. }
  171. return inheritAttributes(el, attributesToInherit);
  172. };
  173. const addEventListener = (el, eventName, callback, opts) => {
  174. return el.addEventListener(eventName, callback, opts);
  175. };
  176. const removeEventListener = (el, eventName, callback, opts) => {
  177. return el.removeEventListener(eventName, callback, opts);
  178. };
  179. /**
  180. * Gets the root context of a shadow dom element
  181. * On newer browsers this will be the shadowRoot,
  182. * but for older browser this may just be the
  183. * element itself.
  184. *
  185. * Useful for whenever you need to explicitly
  186. * do "myElement.shadowRoot!.querySelector(...)".
  187. */
  188. const getElementRoot = (el, fallback = el) => {
  189. return el.shadowRoot || fallback;
  190. };
  191. /**
  192. * Patched version of requestAnimationFrame that avoids ngzone
  193. * Use only when you know ngzone should not run
  194. */
  195. const raf = (h) => {
  196. if (typeof __zone_symbol__requestAnimationFrame === 'function') {
  197. return __zone_symbol__requestAnimationFrame(h);
  198. }
  199. if (typeof requestAnimationFrame === 'function') {
  200. return requestAnimationFrame(h);
  201. }
  202. return setTimeout(h);
  203. };
  204. const hasShadowDom = (el) => {
  205. return !!el.shadowRoot && !!el.attachShadow;
  206. };
  207. const focusVisibleElement = (el) => {
  208. el.focus();
  209. /**
  210. * When programmatically focusing an element,
  211. * the focus-visible utility will not run because
  212. * it is expecting a keyboard event to have triggered this;
  213. * however, there are times when we need to manually control
  214. * this behavior so we call the `setFocus` method on ion-app
  215. * which will let us explicitly set the elements to focus.
  216. */
  217. if (el.classList.contains('ion-focusable')) {
  218. const app = el.closest('ion-app');
  219. if (app) {
  220. app.setFocus([el]);
  221. }
  222. }
  223. };
  224. /**
  225. * This method is used to add a hidden input to a host element that contains
  226. * a Shadow DOM. It does not add the input inside of the Shadow root which
  227. * allows it to be picked up inside of forms. It should contain the same
  228. * values as the host element.
  229. *
  230. * @param always Add a hidden input even if the container does not use Shadow
  231. * @param container The element where the input will be added
  232. * @param name The name of the input
  233. * @param value The value of the input
  234. * @param disabled If true, the input is disabled
  235. */
  236. const renderHiddenInput = (always, container, name, value, disabled) => {
  237. if (always || hasShadowDom(container)) {
  238. let input = container.querySelector('input.aux-input');
  239. if (!input) {
  240. input = container.ownerDocument.createElement('input');
  241. input.type = 'hidden';
  242. input.classList.add('aux-input');
  243. container.appendChild(input);
  244. }
  245. input.disabled = disabled;
  246. input.name = name;
  247. input.value = value || '';
  248. }
  249. };
  250. const clamp = (min, n, max) => {
  251. return Math.max(min, Math.min(n, max));
  252. };
  253. const assert = (actual, reason) => {
  254. if (!actual) {
  255. const message = 'ASSERT: ' + reason;
  256. index.printIonError(message);
  257. debugger; // eslint-disable-line
  258. throw new Error(message);
  259. }
  260. };
  261. const pointerCoord = (ev) => {
  262. // get X coordinates for either a mouse click
  263. // or a touch depending on the given event
  264. if (ev) {
  265. const changedTouches = ev.changedTouches;
  266. if (changedTouches && changedTouches.length > 0) {
  267. const touch = changedTouches[0];
  268. return { x: touch.clientX, y: touch.clientY };
  269. }
  270. if (ev.pageX !== undefined) {
  271. return { x: ev.pageX, y: ev.pageY };
  272. }
  273. }
  274. return { x: 0, y: 0 };
  275. };
  276. /**
  277. * @hidden
  278. * Given a side, return if it should be on the end
  279. * based on the value of dir
  280. * @param side the side
  281. * @param isRTL whether the application dir is rtl
  282. */
  283. const isEndSide = (side) => {
  284. const isRTL = document.dir === 'rtl';
  285. switch (side) {
  286. case 'start':
  287. return isRTL;
  288. case 'end':
  289. return !isRTL;
  290. default:
  291. throw new Error(`"${side}" is not a valid value for [side]. Use "start" or "end" instead.`);
  292. }
  293. };
  294. const debounceEvent = (event, wait) => {
  295. const original = event._original || event;
  296. return {
  297. _original: event,
  298. emit: debounce(original.emit.bind(original), wait),
  299. };
  300. };
  301. const debounce = (func, wait = 0) => {
  302. let timer;
  303. return (...args) => {
  304. clearTimeout(timer);
  305. timer = setTimeout(func, wait, ...args);
  306. };
  307. };
  308. /**
  309. * Check whether the two string maps are shallow equal.
  310. *
  311. * undefined is treated as an empty map.
  312. *
  313. * @returns whether the keys are the same and the values are shallow equal.
  314. */
  315. const shallowEqualStringMap = (map1, map2) => {
  316. map1 !== null && map1 !== void 0 ? map1 : (map1 = {});
  317. map2 !== null && map2 !== void 0 ? map2 : (map2 = {});
  318. if (map1 === map2) {
  319. return true;
  320. }
  321. const keys1 = Object.keys(map1);
  322. if (keys1.length !== Object.keys(map2).length) {
  323. return false;
  324. }
  325. for (const k1 of keys1) {
  326. if (!(k1 in map2)) {
  327. return false;
  328. }
  329. if (map1[k1] !== map2[k1]) {
  330. return false;
  331. }
  332. }
  333. return true;
  334. };
  335. /**
  336. * Checks input for usable number. Not NaN and not Infinite.
  337. */
  338. const isSafeNumber = (input) => {
  339. return typeof input === 'number' && !isNaN(input) && isFinite(input);
  340. };
  341. exports.addEventListener = addEventListener;
  342. exports.assert = assert;
  343. exports.clamp = clamp;
  344. exports.componentOnReady = componentOnReady;
  345. exports.debounce = debounce;
  346. exports.debounceEvent = debounceEvent;
  347. exports.focusVisibleElement = focusVisibleElement;
  348. exports.getElementRoot = getElementRoot;
  349. exports.hasLazyBuild = hasLazyBuild;
  350. exports.hasShadowDom = hasShadowDom;
  351. exports.inheritAriaAttributes = inheritAriaAttributes;
  352. exports.inheritAttributes = inheritAttributes;
  353. exports.isEndSide = isEndSide;
  354. exports.isSafeNumber = isSafeNumber;
  355. exports.pointerCoord = pointerCoord;
  356. exports.raf = raf;
  357. exports.removeEventListener = removeEventListener;
  358. exports.renderHiddenInput = renderHiddenInput;
  359. exports.shallowEqualStringMap = shallowEqualStringMap;
  360. exports.transitionEndAsync = transitionEndAsync;