ion-popover.cjs.entry.js 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323
  1. /*!
  2. * (C) Ionic http://ionicframework.com - MIT License
  3. */
  4. 'use strict';
  5. Object.defineProperty(exports, '__esModule', { value: true });
  6. const index = require('./index-2e236a04.js');
  7. const overlays = require('./overlays-4c291a05.js');
  8. const frameworkDelegate = require('./framework-delegate-862d9d00.js');
  9. const helpers = require('./helpers-8a48fdea.js');
  10. const lockController = require('./lock-controller-6585a42a.js');
  11. const index$1 = require('./index-cc858e97.js');
  12. const ionicGlobal = require('./ionic-global-6dea5a96.js');
  13. const theme = require('./theme-d1c573d2.js');
  14. const index$2 = require('./index-1eff7149.js');
  15. const animation = require('./animation-ab2d3449.js');
  16. require('./index-c8d52405.js');
  17. require('./hardware-back-button-3d2b1004.js');
  18. require('./gesture-controller-9436f482.js');
  19. /**
  20. * Returns the dimensions of the popover
  21. * arrow on `ios` mode. If arrow is disabled
  22. * returns (0, 0).
  23. */
  24. const getArrowDimensions = (arrowEl) => {
  25. if (!arrowEl) {
  26. return { arrowWidth: 0, arrowHeight: 0 };
  27. }
  28. const { width, height } = arrowEl.getBoundingClientRect();
  29. return { arrowWidth: width, arrowHeight: height };
  30. };
  31. /**
  32. * Returns the recommended dimensions of the popover
  33. * that takes into account whether or not the width
  34. * should match the trigger width.
  35. */
  36. const getPopoverDimensions = (size, contentEl, triggerEl) => {
  37. const contentDimentions = contentEl.getBoundingClientRect();
  38. const contentHeight = contentDimentions.height;
  39. let contentWidth = contentDimentions.width;
  40. if (size === 'cover' && triggerEl) {
  41. const triggerDimensions = triggerEl.getBoundingClientRect();
  42. contentWidth = triggerDimensions.width;
  43. }
  44. return {
  45. contentWidth,
  46. contentHeight,
  47. };
  48. };
  49. const configureDismissInteraction = (triggerEl, triggerAction, popoverEl, parentPopoverEl) => {
  50. let dismissCallbacks = [];
  51. const root = helpers.getElementRoot(parentPopoverEl);
  52. const parentContentEl = root.querySelector('.popover-content');
  53. switch (triggerAction) {
  54. case 'hover':
  55. dismissCallbacks = [
  56. {
  57. /**
  58. * Do not use mouseover here
  59. * as this will causes the event to
  60. * be dispatched on each underlying
  61. * element rather than on the popover
  62. * content as a whole.
  63. */
  64. eventName: 'mouseenter',
  65. callback: (ev) => {
  66. /**
  67. * Do not dismiss the popover is we
  68. * are hovering over its trigger.
  69. * This would be easier if we used mouseover
  70. * but this would cause the event to be dispatched
  71. * more often than we would like, potentially
  72. * causing performance issues.
  73. */
  74. const element = document.elementFromPoint(ev.clientX, ev.clientY);
  75. if (element === triggerEl) {
  76. return;
  77. }
  78. popoverEl.dismiss(undefined, undefined, false);
  79. },
  80. },
  81. ];
  82. break;
  83. case 'context-menu':
  84. case 'click':
  85. default:
  86. dismissCallbacks = [
  87. {
  88. eventName: 'click',
  89. callback: (ev) => {
  90. /**
  91. * Do not dismiss the popover is we
  92. * are hovering over its trigger.
  93. */
  94. const target = ev.target;
  95. const closestTrigger = target.closest('[data-ion-popover-trigger]');
  96. if (closestTrigger === triggerEl) {
  97. /**
  98. * stopPropagation here so if the
  99. * popover has dismissOnSelect="true"
  100. * the popover does not dismiss since
  101. * we just clicked a trigger element.
  102. */
  103. ev.stopPropagation();
  104. return;
  105. }
  106. popoverEl.dismiss(undefined, undefined, false);
  107. },
  108. },
  109. ];
  110. break;
  111. }
  112. dismissCallbacks.forEach(({ eventName, callback }) => parentContentEl.addEventListener(eventName, callback));
  113. return () => {
  114. dismissCallbacks.forEach(({ eventName, callback }) => parentContentEl.removeEventListener(eventName, callback));
  115. };
  116. };
  117. /**
  118. * Configures the triggerEl to respond
  119. * to user interaction based upon the triggerAction
  120. * prop that devs have defined.
  121. */
  122. const configureTriggerInteraction = (triggerEl, triggerAction, popoverEl) => {
  123. let triggerCallbacks = [];
  124. /**
  125. * Based upon the kind of trigger interaction
  126. * the user wants, we setup the correct event
  127. * listeners.
  128. */
  129. switch (triggerAction) {
  130. case 'hover':
  131. let hoverTimeout;
  132. triggerCallbacks = [
  133. {
  134. eventName: 'mouseenter',
  135. callback: async (ev) => {
  136. ev.stopPropagation();
  137. if (hoverTimeout) {
  138. clearTimeout(hoverTimeout);
  139. }
  140. /**
  141. * Hovering over a trigger should not
  142. * immediately open the next popover.
  143. */
  144. hoverTimeout = setTimeout(() => {
  145. helpers.raf(() => {
  146. popoverEl.presentFromTrigger(ev);
  147. hoverTimeout = undefined;
  148. });
  149. }, 100);
  150. },
  151. },
  152. {
  153. eventName: 'mouseleave',
  154. callback: (ev) => {
  155. if (hoverTimeout) {
  156. clearTimeout(hoverTimeout);
  157. }
  158. /**
  159. * If mouse is over another popover
  160. * that is not this popover then we should
  161. * close this popover.
  162. */
  163. const target = ev.relatedTarget;
  164. if (!target) {
  165. return;
  166. }
  167. if (target.closest('ion-popover') !== popoverEl) {
  168. popoverEl.dismiss(undefined, undefined, false);
  169. }
  170. },
  171. },
  172. {
  173. /**
  174. * stopPropagation here prevents the popover
  175. * from dismissing when dismiss-on-select="true".
  176. */
  177. eventName: 'click',
  178. callback: (ev) => ev.stopPropagation(),
  179. },
  180. {
  181. eventName: 'ionPopoverActivateTrigger',
  182. callback: (ev) => popoverEl.presentFromTrigger(ev, true),
  183. },
  184. ];
  185. break;
  186. case 'context-menu':
  187. triggerCallbacks = [
  188. {
  189. eventName: 'contextmenu',
  190. callback: (ev) => {
  191. /**
  192. * Prevents the platform context
  193. * menu from appearing.
  194. */
  195. ev.preventDefault();
  196. popoverEl.presentFromTrigger(ev);
  197. },
  198. },
  199. {
  200. eventName: 'click',
  201. callback: (ev) => ev.stopPropagation(),
  202. },
  203. {
  204. eventName: 'ionPopoverActivateTrigger',
  205. callback: (ev) => popoverEl.presentFromTrigger(ev, true),
  206. },
  207. ];
  208. break;
  209. case 'click':
  210. default:
  211. triggerCallbacks = [
  212. {
  213. /**
  214. * Do not do a stopPropagation() here
  215. * because if you had two click triggers
  216. * then clicking the first trigger and then
  217. * clicking the second trigger would not cause
  218. * the first popover to dismiss.
  219. */
  220. eventName: 'click',
  221. callback: (ev) => popoverEl.presentFromTrigger(ev),
  222. },
  223. {
  224. eventName: 'ionPopoverActivateTrigger',
  225. callback: (ev) => popoverEl.presentFromTrigger(ev, true),
  226. },
  227. ];
  228. break;
  229. }
  230. triggerCallbacks.forEach(({ eventName, callback }) => triggerEl.addEventListener(eventName, callback));
  231. triggerEl.setAttribute('data-ion-popover-trigger', 'true');
  232. return () => {
  233. triggerCallbacks.forEach(({ eventName, callback }) => triggerEl.removeEventListener(eventName, callback));
  234. triggerEl.removeAttribute('data-ion-popover-trigger');
  235. };
  236. };
  237. /**
  238. * Returns the index of an ion-item in an array of ion-items.
  239. */
  240. const getIndexOfItem = (items, item) => {
  241. if (!item || item.tagName !== 'ION-ITEM') {
  242. return -1;
  243. }
  244. return items.findIndex((el) => el === item);
  245. };
  246. /**
  247. * Given an array of elements and a currently focused ion-item
  248. * returns the next ion-item relative to the focused one or
  249. * undefined.
  250. */
  251. const getNextItem = (items, currentItem) => {
  252. const currentItemIndex = getIndexOfItem(items, currentItem);
  253. return items[currentItemIndex + 1];
  254. };
  255. /**
  256. * Given an array of elements and a currently focused ion-item
  257. * returns the previous ion-item relative to the focused one or
  258. * undefined.
  259. */
  260. const getPrevItem = (items, currentItem) => {
  261. const currentItemIndex = getIndexOfItem(items, currentItem);
  262. return items[currentItemIndex - 1];
  263. };
  264. /** Focus the internal button of the ion-item */
  265. const focusItem = (item) => {
  266. const root = helpers.getElementRoot(item);
  267. const button = root.querySelector('button');
  268. if (button) {
  269. helpers.raf(() => button.focus());
  270. }
  271. };
  272. /**
  273. * Returns `true` if `el` has been designated
  274. * as a trigger element for an ion-popover.
  275. */
  276. const isTriggerElement = (el) => el.hasAttribute('data-ion-popover-trigger');
  277. const configureKeyboardInteraction = (popoverEl) => {
  278. const callback = async (ev) => {
  279. var _a;
  280. const activeElement = document.activeElement;
  281. let items = [];
  282. const targetTagName = (_a = ev.target) === null || _a === void 0 ? void 0 : _a.tagName;
  283. /**
  284. * Only handle custom keyboard interactions for the host popover element
  285. * and children ion-item elements.
  286. */
  287. if (targetTagName !== 'ION-POPOVER' && targetTagName !== 'ION-ITEM') {
  288. return;
  289. }
  290. /**
  291. * Complex selectors with :not() are :not supported
  292. * in older versions of Chromium so we need to do a
  293. * try/catch here so errors are not thrown.
  294. */
  295. try {
  296. /**
  297. * Select all ion-items that are not children of child popovers.
  298. * i.e. only select ion-item elements that are part of this popover
  299. */
  300. items = Array.from(popoverEl.querySelectorAll('ion-item:not(ion-popover ion-popover *):not([disabled])'));
  301. /* eslint-disable-next-line */
  302. }
  303. catch (_b) { }
  304. switch (ev.key) {
  305. /**
  306. * If we are in a child popover
  307. * then pressing the left arrow key
  308. * should close this popover and move
  309. * focus to the popover that presented
  310. * this one.
  311. */
  312. case 'ArrowLeft':
  313. const parentPopover = await popoverEl.getParentPopover();
  314. if (parentPopover) {
  315. popoverEl.dismiss(undefined, undefined, false);
  316. }
  317. break;
  318. /**
  319. * ArrowDown should move focus to the next focusable ion-item.
  320. */
  321. case 'ArrowDown':
  322. // Disable movement/scroll with keyboard
  323. ev.preventDefault();
  324. const nextItem = getNextItem(items, activeElement);
  325. if (nextItem !== undefined) {
  326. focusItem(nextItem);
  327. }
  328. break;
  329. /**
  330. * ArrowUp should move focus to the previous focusable ion-item.
  331. */
  332. case 'ArrowUp':
  333. // Disable movement/scroll with keyboard
  334. ev.preventDefault();
  335. const prevItem = getPrevItem(items, activeElement);
  336. if (prevItem !== undefined) {
  337. focusItem(prevItem);
  338. }
  339. break;
  340. /**
  341. * Home should move focus to the first focusable ion-item.
  342. */
  343. case 'Home':
  344. ev.preventDefault();
  345. const firstItem = items[0];
  346. if (firstItem !== undefined) {
  347. focusItem(firstItem);
  348. }
  349. break;
  350. /**
  351. * End should move focus to the last focusable ion-item.
  352. */
  353. case 'End':
  354. ev.preventDefault();
  355. const lastItem = items[items.length - 1];
  356. if (lastItem !== undefined) {
  357. focusItem(lastItem);
  358. }
  359. break;
  360. /**
  361. * ArrowRight, Spacebar, or Enter should activate
  362. * the currently focused trigger item to open a
  363. * popover if the element is a trigger item.
  364. */
  365. case 'ArrowRight':
  366. case ' ':
  367. case 'Enter':
  368. if (activeElement && isTriggerElement(activeElement)) {
  369. const rightEvent = new CustomEvent('ionPopoverActivateTrigger');
  370. activeElement.dispatchEvent(rightEvent);
  371. }
  372. break;
  373. }
  374. };
  375. popoverEl.addEventListener('keydown', callback);
  376. return () => popoverEl.removeEventListener('keydown', callback);
  377. };
  378. /**
  379. * Positions a popover by taking into account
  380. * the reference point, preferred side, alignment
  381. * and viewport dimensions.
  382. */
  383. const getPopoverPosition = (isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, triggerEl, event) => {
  384. var _a;
  385. let referenceCoordinates = {
  386. top: 0,
  387. left: 0,
  388. width: 0,
  389. height: 0,
  390. };
  391. /**
  392. * Calculate position relative to the
  393. * x-y coordinates in the event that
  394. * was passed in
  395. */
  396. switch (reference) {
  397. case 'event':
  398. if (!event) {
  399. return defaultPosition;
  400. }
  401. const mouseEv = event;
  402. referenceCoordinates = {
  403. top: mouseEv.clientY,
  404. left: mouseEv.clientX,
  405. width: 1,
  406. height: 1,
  407. };
  408. break;
  409. /**
  410. * Calculate position relative to the bounding
  411. * box on either the trigger element
  412. * specified via the `trigger` prop or
  413. * the target specified on the event
  414. * that was passed in.
  415. */
  416. case 'trigger':
  417. default:
  418. const customEv = event;
  419. /**
  420. * ionShadowTarget is used when we need to align the
  421. * popover with an element inside of the shadow root
  422. * of an Ionic component. Ex: Presenting a popover
  423. * by clicking on the collapsed indicator inside
  424. * of `ion-breadcrumb` and centering it relative
  425. * to the indicator rather than `ion-breadcrumb`
  426. * as a whole.
  427. */
  428. const actualTriggerEl = (triggerEl ||
  429. ((_a = customEv === null || customEv === void 0 ? void 0 : customEv.detail) === null || _a === void 0 ? void 0 : _a.ionShadowTarget) ||
  430. (customEv === null || customEv === void 0 ? void 0 : customEv.target));
  431. if (!actualTriggerEl) {
  432. return defaultPosition;
  433. }
  434. const triggerBoundingBox = actualTriggerEl.getBoundingClientRect();
  435. referenceCoordinates = {
  436. top: triggerBoundingBox.top,
  437. left: triggerBoundingBox.left,
  438. width: triggerBoundingBox.width,
  439. height: triggerBoundingBox.height,
  440. };
  441. break;
  442. }
  443. /**
  444. * Get top/left offset that would allow
  445. * popover to be positioned on the
  446. * preferred side of the reference.
  447. */
  448. const coordinates = calculatePopoverSide(side, referenceCoordinates, contentWidth, contentHeight, arrowWidth, arrowHeight, isRTL);
  449. /**
  450. * Get the top/left adjustments that
  451. * would allow the popover content
  452. * to have the correct alignment.
  453. */
  454. const alignedCoordinates = calculatePopoverAlign(align, side, referenceCoordinates, contentWidth, contentHeight);
  455. const top = coordinates.top + alignedCoordinates.top;
  456. const left = coordinates.left + alignedCoordinates.left;
  457. const { arrowTop, arrowLeft } = calculateArrowPosition(side, arrowWidth, arrowHeight, top, left, contentWidth, contentHeight, isRTL);
  458. const { originX, originY } = calculatePopoverOrigin(side, align, isRTL);
  459. return { top, left, referenceCoordinates, arrowTop, arrowLeft, originX, originY };
  460. };
  461. /**
  462. * Determines the transform-origin
  463. * of the popover animation so that it
  464. * is in line with what the side and alignment
  465. * prop values are. Currently only used
  466. * with the MD animation.
  467. */
  468. const calculatePopoverOrigin = (side, align, isRTL) => {
  469. switch (side) {
  470. case 'top':
  471. return { originX: getOriginXAlignment(align), originY: 'bottom' };
  472. case 'bottom':
  473. return { originX: getOriginXAlignment(align), originY: 'top' };
  474. case 'left':
  475. return { originX: 'right', originY: getOriginYAlignment(align) };
  476. case 'right':
  477. return { originX: 'left', originY: getOriginYAlignment(align) };
  478. case 'start':
  479. return { originX: isRTL ? 'left' : 'right', originY: getOriginYAlignment(align) };
  480. case 'end':
  481. return { originX: isRTL ? 'right' : 'left', originY: getOriginYAlignment(align) };
  482. }
  483. };
  484. const getOriginXAlignment = (align) => {
  485. switch (align) {
  486. case 'start':
  487. return 'left';
  488. case 'center':
  489. return 'center';
  490. case 'end':
  491. return 'right';
  492. }
  493. };
  494. const getOriginYAlignment = (align) => {
  495. switch (align) {
  496. case 'start':
  497. return 'top';
  498. case 'center':
  499. return 'center';
  500. case 'end':
  501. return 'bottom';
  502. }
  503. };
  504. /**
  505. * Calculates where the arrow positioning
  506. * should be relative to the popover content.
  507. */
  508. const calculateArrowPosition = (side, arrowWidth, arrowHeight, top, left, contentWidth, contentHeight, isRTL) => {
  509. /**
  510. * Note: When side is left, right, start, or end, the arrow is
  511. * been rotated using a `transform`, so to move the arrow up or down
  512. * by its dimension, you need to use `arrowWidth`.
  513. */
  514. const leftPosition = {
  515. arrowTop: top + contentHeight / 2 - arrowWidth / 2,
  516. arrowLeft: left + contentWidth - arrowWidth / 2,
  517. };
  518. /**
  519. * Move the arrow to the left by arrowWidth and then
  520. * again by half of its width because we have rotated
  521. * the arrow using a transform.
  522. */
  523. const rightPosition = { arrowTop: top + contentHeight / 2 - arrowWidth / 2, arrowLeft: left - arrowWidth * 1.5 };
  524. switch (side) {
  525. case 'top':
  526. return { arrowTop: top + contentHeight, arrowLeft: left + contentWidth / 2 - arrowWidth / 2 };
  527. case 'bottom':
  528. return { arrowTop: top - arrowHeight, arrowLeft: left + contentWidth / 2 - arrowWidth / 2 };
  529. case 'left':
  530. return leftPosition;
  531. case 'right':
  532. return rightPosition;
  533. case 'start':
  534. return isRTL ? rightPosition : leftPosition;
  535. case 'end':
  536. return isRTL ? leftPosition : rightPosition;
  537. default:
  538. return { arrowTop: 0, arrowLeft: 0 };
  539. }
  540. };
  541. /**
  542. * Calculates the required top/left
  543. * values needed to position the popover
  544. * content on the side specified in the
  545. * `side` prop.
  546. */
  547. const calculatePopoverSide = (side, triggerBoundingBox, contentWidth, contentHeight, arrowWidth, arrowHeight, isRTL) => {
  548. const sideLeft = {
  549. top: triggerBoundingBox.top,
  550. left: triggerBoundingBox.left - contentWidth - arrowWidth,
  551. };
  552. const sideRight = {
  553. top: triggerBoundingBox.top,
  554. left: triggerBoundingBox.left + triggerBoundingBox.width + arrowWidth,
  555. };
  556. switch (side) {
  557. case 'top':
  558. return {
  559. top: triggerBoundingBox.top - contentHeight - arrowHeight,
  560. left: triggerBoundingBox.left,
  561. };
  562. case 'right':
  563. return sideRight;
  564. case 'bottom':
  565. return {
  566. top: triggerBoundingBox.top + triggerBoundingBox.height + arrowHeight,
  567. left: triggerBoundingBox.left,
  568. };
  569. case 'left':
  570. return sideLeft;
  571. case 'start':
  572. return isRTL ? sideRight : sideLeft;
  573. case 'end':
  574. return isRTL ? sideLeft : sideRight;
  575. }
  576. };
  577. /**
  578. * Calculates the required top/left
  579. * offset values needed to provide the
  580. * correct alignment regardless while taking
  581. * into account the side the popover is on.
  582. */
  583. const calculatePopoverAlign = (align, side, triggerBoundingBox, contentWidth, contentHeight) => {
  584. switch (align) {
  585. case 'center':
  586. return calculatePopoverCenterAlign(side, triggerBoundingBox, contentWidth, contentHeight);
  587. case 'end':
  588. return calculatePopoverEndAlign(side, triggerBoundingBox, contentWidth, contentHeight);
  589. case 'start':
  590. default:
  591. return { top: 0, left: 0 };
  592. }
  593. };
  594. /**
  595. * Calculate the end alignment for
  596. * the popover. If side is on the x-axis
  597. * then the align values refer to the top
  598. * and bottom margins of the content.
  599. * If side is on the y-axis then the
  600. * align values refer to the left and right
  601. * margins of the content.
  602. */
  603. const calculatePopoverEndAlign = (side, triggerBoundingBox, contentWidth, contentHeight) => {
  604. switch (side) {
  605. case 'start':
  606. case 'end':
  607. case 'left':
  608. case 'right':
  609. return {
  610. top: -(contentHeight - triggerBoundingBox.height),
  611. left: 0,
  612. };
  613. case 'top':
  614. case 'bottom':
  615. default:
  616. return {
  617. top: 0,
  618. left: -(contentWidth - triggerBoundingBox.width),
  619. };
  620. }
  621. };
  622. /**
  623. * Calculate the center alignment for
  624. * the popover. If side is on the x-axis
  625. * then the align values refer to the top
  626. * and bottom margins of the content.
  627. * If side is on the y-axis then the
  628. * align values refer to the left and right
  629. * margins of the content.
  630. */
  631. const calculatePopoverCenterAlign = (side, triggerBoundingBox, contentWidth, contentHeight) => {
  632. switch (side) {
  633. case 'start':
  634. case 'end':
  635. case 'left':
  636. case 'right':
  637. return {
  638. top: -(contentHeight / 2 - triggerBoundingBox.height / 2),
  639. left: 0,
  640. };
  641. case 'top':
  642. case 'bottom':
  643. default:
  644. return {
  645. top: 0,
  646. left: -(contentWidth / 2 - triggerBoundingBox.width / 2),
  647. };
  648. }
  649. };
  650. /**
  651. * Adjusts popover positioning coordinates
  652. * such that popover does not appear offscreen
  653. * or overlapping safe area bounds.
  654. */
  655. const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeAreaMargin, contentOriginX, contentOriginY, triggerCoordinates, coordArrowTop = 0, coordArrowLeft = 0, arrowHeight = 0) => {
  656. let arrowTop = coordArrowTop;
  657. const arrowLeft = coordArrowLeft;
  658. let left = coordLeft;
  659. let top = coordTop;
  660. let bottom;
  661. let originX = contentOriginX;
  662. let originY = contentOriginY;
  663. let checkSafeAreaLeft = false;
  664. let checkSafeAreaRight = false;
  665. const triggerTop = triggerCoordinates
  666. ? triggerCoordinates.top + triggerCoordinates.height
  667. : bodyHeight / 2 - contentHeight / 2;
  668. const triggerHeight = triggerCoordinates ? triggerCoordinates.height : 0;
  669. let addPopoverBottomClass = false;
  670. /**
  671. * Adjust popover so it does not
  672. * go off the left of the screen.
  673. */
  674. if (left < bodyPadding + safeAreaMargin) {
  675. left = bodyPadding;
  676. checkSafeAreaLeft = true;
  677. originX = 'left';
  678. /**
  679. * Adjust popover so it does not
  680. * go off the right of the screen.
  681. */
  682. }
  683. else if (contentWidth + bodyPadding + left + safeAreaMargin > bodyWidth) {
  684. checkSafeAreaRight = true;
  685. left = bodyWidth - contentWidth - bodyPadding;
  686. originX = 'right';
  687. }
  688. /**
  689. * Adjust popover so it does not
  690. * go off the top of the screen.
  691. * If popover is on the left or the right of
  692. * the trigger, then we should not adjust top
  693. * margins.
  694. */
  695. if (triggerTop + triggerHeight + contentHeight > bodyHeight && (side === 'top' || side === 'bottom')) {
  696. if (triggerTop - contentHeight > 0) {
  697. /**
  698. * While we strive to align the popover with the trigger
  699. * on smaller screens this is not always possible. As a result,
  700. * we adjust the popover up so that it does not hang
  701. * off the bottom of the screen. However, we do not want to move
  702. * the popover up so much that it goes off the top of the screen.
  703. *
  704. * We chose 12 here so that the popover position looks a bit nicer as
  705. * it is not right up against the edge of the screen.
  706. */
  707. top = Math.max(12, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
  708. arrowTop = top + contentHeight;
  709. originY = 'bottom';
  710. addPopoverBottomClass = true;
  711. /**
  712. * If not enough room for popover to appear
  713. * above trigger, then cut it off.
  714. */
  715. }
  716. else {
  717. bottom = bodyPadding;
  718. }
  719. }
  720. return {
  721. top,
  722. left,
  723. bottom,
  724. originX,
  725. originY,
  726. checkSafeAreaLeft,
  727. checkSafeAreaRight,
  728. arrowTop,
  729. arrowLeft,
  730. addPopoverBottomClass,
  731. };
  732. };
  733. const shouldShowArrow = (side, didAdjustBounds = false, ev, trigger) => {
  734. /**
  735. * If no event provided and
  736. * we do not have a trigger,
  737. * then this popover was likely
  738. * presented via the popoverController
  739. * or users called `present` manually.
  740. * In this case, the arrow should not be
  741. * shown as we do not have a reference.
  742. */
  743. if (!ev && !trigger) {
  744. return false;
  745. }
  746. /**
  747. * If popover is on the left or the right
  748. * of a trigger, but we needed to adjust the
  749. * popover due to screen bounds, then we should
  750. * hide the arrow as it will never be pointing
  751. * at the trigger.
  752. */
  753. if (side !== 'top' && side !== 'bottom' && didAdjustBounds) {
  754. return false;
  755. }
  756. return true;
  757. };
  758. const POPOVER_IOS_BODY_PADDING = 5;
  759. /**
  760. * iOS Popover Enter Animation
  761. */
  762. // TODO(FW-2832): types
  763. const iosEnterAnimation = (baseEl, opts) => {
  764. var _a;
  765. const { event: ev, size, trigger, reference, side, align } = opts;
  766. const doc = baseEl.ownerDocument;
  767. const isRTL = doc.dir === 'rtl';
  768. const bodyWidth = doc.defaultView.innerWidth;
  769. const bodyHeight = doc.defaultView.innerHeight;
  770. const root = helpers.getElementRoot(baseEl);
  771. const contentEl = root.querySelector('.popover-content');
  772. const arrowEl = root.querySelector('.popover-arrow');
  773. const referenceSizeEl = trigger || ((_a = ev === null || ev === void 0 ? void 0 : ev.detail) === null || _a === void 0 ? void 0 : _a.ionShadowTarget) || (ev === null || ev === void 0 ? void 0 : ev.target);
  774. const { contentWidth, contentHeight } = getPopoverDimensions(size, contentEl, referenceSizeEl);
  775. const { arrowWidth, arrowHeight } = getArrowDimensions(arrowEl);
  776. const defaultPosition = {
  777. top: bodyHeight / 2 - contentHeight / 2,
  778. left: bodyWidth / 2 - contentWidth / 2,
  779. originX: isRTL ? 'right' : 'left',
  780. originY: 'top',
  781. };
  782. const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
  783. const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
  784. const margin = size === 'cover' ? 0 : 25;
  785. const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, arrowTop, arrowLeft, addPopoverBottomClass, } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, margin, results.originX, results.originY, results.referenceCoordinates, results.arrowTop, results.arrowLeft, arrowHeight);
  786. const baseAnimation = animation.createAnimation();
  787. const backdropAnimation = animation.createAnimation();
  788. const contentAnimation = animation.createAnimation();
  789. backdropAnimation
  790. .addElement(root.querySelector('ion-backdrop'))
  791. .fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
  792. .beforeStyles({
  793. 'pointer-events': 'none',
  794. })
  795. .afterClearStyles(['pointer-events']);
  796. // In Chromium, if the wrapper animates, the backdrop filter doesn't work.
  797. // The Chromium team stated that this behavior is expected and not a bug. The element animating opacity creates a backdrop root for the backdrop-filter.
  798. // To get around this, instead of animating the wrapper, animate both the arrow and content.
  799. // https://bugs.chromium.org/p/chromium/issues/detail?id=1148826
  800. contentAnimation
  801. .addElement(root.querySelector('.popover-arrow'))
  802. .addElement(root.querySelector('.popover-content'))
  803. .fromTo('opacity', 0.01, 1);
  804. // TODO(FW-4376) Ensure that arrow also blurs when translucent
  805. return baseAnimation
  806. .easing('ease')
  807. .duration(100)
  808. .beforeAddWrite(() => {
  809. if (size === 'cover') {
  810. baseEl.style.setProperty('--width', `${contentWidth}px`);
  811. }
  812. if (addPopoverBottomClass) {
  813. baseEl.classList.add('popover-bottom');
  814. }
  815. if (bottom !== undefined) {
  816. contentEl.style.setProperty('bottom', `${bottom}px`);
  817. }
  818. const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
  819. const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
  820. let leftValue = `${left}px`;
  821. if (checkSafeAreaLeft) {
  822. leftValue = `${left}px${safeAreaLeft}`;
  823. }
  824. if (checkSafeAreaRight) {
  825. leftValue = `${left}px${safeAreaRight}`;
  826. }
  827. contentEl.style.setProperty('top', `calc(${top}px + var(--offset-y, 0))`);
  828. contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
  829. contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
  830. if (arrowEl !== null) {
  831. const didAdjustBounds = results.top !== top || results.left !== left;
  832. const showArrow = shouldShowArrow(side, didAdjustBounds, ev, trigger);
  833. if (showArrow) {
  834. arrowEl.style.setProperty('top', `calc(${arrowTop}px + var(--offset-y, 0))`);
  835. arrowEl.style.setProperty('left', `calc(${arrowLeft}px + var(--offset-x, 0))`);
  836. }
  837. else {
  838. arrowEl.style.setProperty('display', 'none');
  839. }
  840. }
  841. })
  842. .addAnimation([backdropAnimation, contentAnimation]);
  843. };
  844. /**
  845. * iOS Popover Leave Animation
  846. */
  847. const iosLeaveAnimation = (baseEl) => {
  848. const root = helpers.getElementRoot(baseEl);
  849. const contentEl = root.querySelector('.popover-content');
  850. const arrowEl = root.querySelector('.popover-arrow');
  851. const baseAnimation = animation.createAnimation();
  852. const backdropAnimation = animation.createAnimation();
  853. const contentAnimation = animation.createAnimation();
  854. backdropAnimation.addElement(root.querySelector('ion-backdrop')).fromTo('opacity', 'var(--backdrop-opacity)', 0);
  855. contentAnimation
  856. .addElement(root.querySelector('.popover-arrow'))
  857. .addElement(root.querySelector('.popover-content'))
  858. .fromTo('opacity', 0.99, 0);
  859. return baseAnimation
  860. .easing('ease')
  861. .afterAddWrite(() => {
  862. baseEl.style.removeProperty('--width');
  863. baseEl.classList.remove('popover-bottom');
  864. contentEl.style.removeProperty('top');
  865. contentEl.style.removeProperty('left');
  866. contentEl.style.removeProperty('bottom');
  867. contentEl.style.removeProperty('transform-origin');
  868. if (arrowEl) {
  869. arrowEl.style.removeProperty('top');
  870. arrowEl.style.removeProperty('left');
  871. arrowEl.style.removeProperty('display');
  872. }
  873. })
  874. .duration(300)
  875. .addAnimation([backdropAnimation, contentAnimation]);
  876. };
  877. const POPOVER_MD_BODY_PADDING = 12;
  878. /**
  879. * Md Popover Enter Animation
  880. */
  881. // TODO(FW-2832): types
  882. const mdEnterAnimation = (baseEl, opts) => {
  883. var _a;
  884. const { event: ev, size, trigger, reference, side, align } = opts;
  885. const doc = baseEl.ownerDocument;
  886. const isRTL = doc.dir === 'rtl';
  887. const bodyWidth = doc.defaultView.innerWidth;
  888. const bodyHeight = doc.defaultView.innerHeight;
  889. const root = helpers.getElementRoot(baseEl);
  890. const contentEl = root.querySelector('.popover-content');
  891. const referenceSizeEl = trigger || ((_a = ev === null || ev === void 0 ? void 0 : ev.detail) === null || _a === void 0 ? void 0 : _a.ionShadowTarget) || (ev === null || ev === void 0 ? void 0 : ev.target);
  892. const { contentWidth, contentHeight } = getPopoverDimensions(size, contentEl, referenceSizeEl);
  893. const defaultPosition = {
  894. top: bodyHeight / 2 - contentHeight / 2,
  895. left: bodyWidth / 2 - contentWidth / 2,
  896. originX: isRTL ? 'right' : 'left',
  897. originY: 'top',
  898. };
  899. const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
  900. const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
  901. const { originX, originY, top, left, bottom } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
  902. const baseAnimation = animation.createAnimation();
  903. const backdropAnimation = animation.createAnimation();
  904. const wrapperAnimation = animation.createAnimation();
  905. const contentAnimation = animation.createAnimation();
  906. const viewportAnimation = animation.createAnimation();
  907. backdropAnimation
  908. .addElement(root.querySelector('ion-backdrop'))
  909. .fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
  910. .beforeStyles({
  911. 'pointer-events': 'none',
  912. })
  913. .afterClearStyles(['pointer-events']);
  914. wrapperAnimation.addElement(root.querySelector('.popover-wrapper')).duration(150).fromTo('opacity', 0.01, 1);
  915. contentAnimation
  916. .addElement(contentEl)
  917. .beforeStyles({
  918. top: `calc(${top}px + var(--offset-y, 0px))`,
  919. left: `calc(${left}px + var(--offset-x, 0px))`,
  920. 'transform-origin': `${originY} ${originX}`,
  921. })
  922. .beforeAddWrite(() => {
  923. if (bottom !== undefined) {
  924. contentEl.style.setProperty('bottom', `${bottom}px`);
  925. }
  926. })
  927. .fromTo('transform', 'scale(0.8)', 'scale(1)');
  928. viewportAnimation.addElement(root.querySelector('.popover-viewport')).fromTo('opacity', 0.01, 1);
  929. return baseAnimation
  930. .easing('cubic-bezier(0.36,0.66,0.04,1)')
  931. .duration(300)
  932. .beforeAddWrite(() => {
  933. if (size === 'cover') {
  934. baseEl.style.setProperty('--width', `${contentWidth}px`);
  935. }
  936. if (originY === 'bottom') {
  937. baseEl.classList.add('popover-bottom');
  938. }
  939. })
  940. .addAnimation([backdropAnimation, wrapperAnimation, contentAnimation, viewportAnimation]);
  941. };
  942. /**
  943. * Md Popover Leave Animation
  944. */
  945. const mdLeaveAnimation = (baseEl) => {
  946. const root = helpers.getElementRoot(baseEl);
  947. const contentEl = root.querySelector('.popover-content');
  948. const baseAnimation = animation.createAnimation();
  949. const backdropAnimation = animation.createAnimation();
  950. const wrapperAnimation = animation.createAnimation();
  951. backdropAnimation.addElement(root.querySelector('ion-backdrop')).fromTo('opacity', 'var(--backdrop-opacity)', 0);
  952. wrapperAnimation.addElement(root.querySelector('.popover-wrapper')).fromTo('opacity', 0.99, 0);
  953. return baseAnimation
  954. .easing('ease')
  955. .afterAddWrite(() => {
  956. baseEl.style.removeProperty('--width');
  957. baseEl.classList.remove('popover-bottom');
  958. contentEl.style.removeProperty('top');
  959. contentEl.style.removeProperty('left');
  960. contentEl.style.removeProperty('bottom');
  961. contentEl.style.removeProperty('transform-origin');
  962. })
  963. .duration(150)
  964. .addAnimation([backdropAnimation, wrapperAnimation]);
  965. };
  966. const popoverIosCss = ":host{--background:var(--ion-background-color, #fff);--min-width:0;--min-height:0;--max-width:auto;--height:auto;--offset-x:0px;--offset-y:0px;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:fixed;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);z-index:1001}:host(.popover-nested){pointer-events:none}:host(.popover-nested) .popover-wrapper{pointer-events:auto}:host(.overlay-hidden){display:none}.popover-wrapper{z-index:10}.popover-content{display:-ms-flexbox;display:flex;position:absolute;-ms-flex-direction:column;flex-direction:column;width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:auto;z-index:10}::slotted(.popover-viewport){--ion-safe-area-top:0px;--ion-safe-area-right:0px;--ion-safe-area-bottom:0px;--ion-safe-area-left:0px;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}:host(.popover-nested.popover-side-left){--offset-x:5px}:host(.popover-nested.popover-side-right){--offset-x:-5px}:host(.popover-nested.popover-side-start){--offset-x:5px}:host-context([dir=rtl]):host(.popover-nested.popover-side-start),:host-context([dir=rtl]).popover-nested.popover-side-start{--offset-x:-5px}@supports selector(:dir(rtl)){:host(.popover-nested.popover-side-start:dir(rtl)){--offset-x:-5px}}:host(.popover-nested.popover-side-end){--offset-x:-5px}:host-context([dir=rtl]):host(.popover-nested.popover-side-end),:host-context([dir=rtl]).popover-nested.popover-side-end{--offset-x:5px}@supports selector(:dir(rtl)){:host(.popover-nested.popover-side-end:dir(rtl)){--offset-x:5px}}:host{--width:200px;--max-height:90%;--box-shadow:none;--backdrop-opacity:var(--ion-backdrop-opacity, 0.08)}:host(.popover-desktop){--box-shadow:0px 4px 16px 0px rgba(0, 0, 0, 0.12)}.popover-content{border-radius:10px}:host(.popover-desktop) .popover-content{border:0.5px solid var(--ion-color-step-100, var(--ion-background-color-step-100, #e6e6e6))}.popover-arrow{display:block;position:absolute;width:20px;height:10px;overflow:hidden;z-index:11}.popover-arrow::after{top:3px;border-radius:3px;position:absolute;width:14px;height:14px;-webkit-transform:rotate(45deg);transform:rotate(45deg);background:var(--background);content:\"\";z-index:10}.popover-arrow::after{inset-inline-start:3px}:host(.popover-bottom) .popover-arrow{top:auto;bottom:-10px}:host(.popover-bottom) .popover-arrow::after{top:-6px}:host(.popover-side-left) .popover-arrow{-webkit-transform:rotate(90deg);transform:rotate(90deg)}:host(.popover-side-right) .popover-arrow{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}:host(.popover-side-top) .popover-arrow{-webkit-transform:rotate(180deg);transform:rotate(180deg)}:host(.popover-side-start) .popover-arrow{-webkit-transform:rotate(90deg);transform:rotate(90deg)}:host-context([dir=rtl]):host(.popover-side-start) .popover-arrow,:host-context([dir=rtl]).popover-side-start .popover-arrow{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}@supports selector(:dir(rtl)){:host(.popover-side-start:dir(rtl)) .popover-arrow{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}}:host(.popover-side-end) .popover-arrow{-webkit-transform:rotate(-90deg);transform:rotate(-90deg)}:host-context([dir=rtl]):host(.popover-side-end) .popover-arrow,:host-context([dir=rtl]).popover-side-end .popover-arrow{-webkit-transform:rotate(90deg);transform:rotate(90deg)}@supports selector(:dir(rtl)){:host(.popover-side-end:dir(rtl)) .popover-arrow{-webkit-transform:rotate(90deg);transform:rotate(90deg)}}.popover-arrow,.popover-content{opacity:0}@supports ((-webkit-backdrop-filter: blur(0)) or (backdrop-filter: blur(0))){:host(.popover-translucent) .popover-content,:host(.popover-translucent) .popover-arrow::after{background:rgba(var(--ion-background-color-rgb, 255, 255, 255), 0.8);-webkit-backdrop-filter:saturate(180%) blur(20px);backdrop-filter:saturate(180%) blur(20px)}}";
  967. const IonPopoverIosStyle0 = popoverIosCss;
  968. const popoverMdCss = ":host{--background:var(--ion-background-color, #fff);--min-width:0;--min-height:0;--max-width:auto;--height:auto;--offset-x:0px;--offset-y:0px;left:0;right:0;top:0;bottom:0;display:-ms-flexbox;display:flex;position:fixed;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;outline:none;color:var(--ion-text-color, #000);z-index:1001}:host(.popover-nested){pointer-events:none}:host(.popover-nested) .popover-wrapper{pointer-events:auto}:host(.overlay-hidden){display:none}.popover-wrapper{z-index:10}.popover-content{display:-ms-flexbox;display:flex;position:absolute;-ms-flex-direction:column;flex-direction:column;width:var(--width);min-width:var(--min-width);max-width:var(--max-width);height:var(--height);min-height:var(--min-height);max-height:var(--max-height);background:var(--background);-webkit-box-shadow:var(--box-shadow);box-shadow:var(--box-shadow);overflow:auto;z-index:10}::slotted(.popover-viewport){--ion-safe-area-top:0px;--ion-safe-area-right:0px;--ion-safe-area-bottom:0px;--ion-safe-area-left:0px;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}:host(.popover-nested.popover-side-left){--offset-x:5px}:host(.popover-nested.popover-side-right){--offset-x:-5px}:host(.popover-nested.popover-side-start){--offset-x:5px}:host-context([dir=rtl]):host(.popover-nested.popover-side-start),:host-context([dir=rtl]).popover-nested.popover-side-start{--offset-x:-5px}@supports selector(:dir(rtl)){:host(.popover-nested.popover-side-start:dir(rtl)){--offset-x:-5px}}:host(.popover-nested.popover-side-end){--offset-x:-5px}:host-context([dir=rtl]):host(.popover-nested.popover-side-end),:host-context([dir=rtl]).popover-nested.popover-side-end{--offset-x:5px}@supports selector(:dir(rtl)){:host(.popover-nested.popover-side-end:dir(rtl)){--offset-x:5px}}:host{--width:250px;--max-height:90%;--box-shadow:0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);--backdrop-opacity:var(--ion-backdrop-opacity, 0.32)}.popover-content{border-radius:4px;-webkit-transform-origin:left top;transform-origin:left top}:host-context([dir=rtl]) .popover-content{-webkit-transform-origin:right top;transform-origin:right top}[dir=rtl] .popover-content{-webkit-transform-origin:right top;transform-origin:right top}@supports selector(:dir(rtl)){.popover-content:dir(rtl){-webkit-transform-origin:right top;transform-origin:right top}}.popover-viewport{-webkit-transition-delay:100ms;transition-delay:100ms}.popover-wrapper{opacity:0}";
  969. const IonPopoverMdStyle0 = popoverMdCss;
  970. const Popover = class {
  971. constructor(hostRef) {
  972. index.registerInstance(this, hostRef);
  973. this.didPresent = index.createEvent(this, "ionPopoverDidPresent", 7);
  974. this.willPresent = index.createEvent(this, "ionPopoverWillPresent", 7);
  975. this.willDismiss = index.createEvent(this, "ionPopoverWillDismiss", 7);
  976. this.didDismiss = index.createEvent(this, "ionPopoverDidDismiss", 7);
  977. this.didPresentShorthand = index.createEvent(this, "didPresent", 7);
  978. this.willPresentShorthand = index.createEvent(this, "willPresent", 7);
  979. this.willDismissShorthand = index.createEvent(this, "willDismiss", 7);
  980. this.didDismissShorthand = index.createEvent(this, "didDismiss", 7);
  981. this.ionMount = index.createEvent(this, "ionMount", 7);
  982. this.parentPopover = null;
  983. this.coreDelegate = frameworkDelegate.CoreDelegate();
  984. this.lockController = lockController.createLockController();
  985. this.inline = false;
  986. this.focusDescendantOnPresent = false;
  987. this.onBackdropTap = () => {
  988. this.dismiss(undefined, overlays.BACKDROP);
  989. };
  990. this.onLifecycle = (modalEvent) => {
  991. const el = this.usersElement;
  992. const name = LIFECYCLE_MAP[modalEvent.type];
  993. if (el && name) {
  994. const event = new CustomEvent(name, {
  995. bubbles: false,
  996. cancelable: false,
  997. detail: modalEvent.detail,
  998. });
  999. el.dispatchEvent(event);
  1000. }
  1001. };
  1002. this.configureTriggerInteraction = () => {
  1003. const { trigger, triggerAction, el, destroyTriggerInteraction } = this;
  1004. if (destroyTriggerInteraction) {
  1005. destroyTriggerInteraction();
  1006. }
  1007. if (trigger === undefined) {
  1008. return;
  1009. }
  1010. const triggerEl = (this.triggerEl = trigger !== undefined ? document.getElementById(trigger) : null);
  1011. if (!triggerEl) {
  1012. index$1.printIonWarning(`[ion-popover] - A trigger element with the ID "${trigger}" was not found in the DOM. The trigger element must be in the DOM when the "trigger" property is set on ion-popover.`, this.el);
  1013. return;
  1014. }
  1015. this.destroyTriggerInteraction = configureTriggerInteraction(triggerEl, triggerAction, el);
  1016. };
  1017. this.configureKeyboardInteraction = () => {
  1018. const { destroyKeyboardInteraction, el } = this;
  1019. if (destroyKeyboardInteraction) {
  1020. destroyKeyboardInteraction();
  1021. }
  1022. this.destroyKeyboardInteraction = configureKeyboardInteraction(el);
  1023. };
  1024. this.configureDismissInteraction = () => {
  1025. const { destroyDismissInteraction, parentPopover, triggerAction, triggerEl, el } = this;
  1026. if (!parentPopover || !triggerEl) {
  1027. return;
  1028. }
  1029. if (destroyDismissInteraction) {
  1030. destroyDismissInteraction();
  1031. }
  1032. this.destroyDismissInteraction = configureDismissInteraction(triggerEl, triggerAction, el, parentPopover);
  1033. };
  1034. this.presented = false;
  1035. this.hasController = false;
  1036. this.delegate = undefined;
  1037. this.overlayIndex = undefined;
  1038. this.enterAnimation = undefined;
  1039. this.leaveAnimation = undefined;
  1040. this.component = undefined;
  1041. this.componentProps = undefined;
  1042. this.keyboardClose = true;
  1043. this.cssClass = undefined;
  1044. this.backdropDismiss = true;
  1045. this.event = undefined;
  1046. this.showBackdrop = true;
  1047. this.translucent = false;
  1048. this.animated = true;
  1049. this.htmlAttributes = undefined;
  1050. this.triggerAction = 'click';
  1051. this.trigger = undefined;
  1052. this.size = 'auto';
  1053. this.dismissOnSelect = false;
  1054. this.reference = 'trigger';
  1055. this.side = 'bottom';
  1056. this.alignment = undefined;
  1057. this.arrow = true;
  1058. this.isOpen = false;
  1059. this.keyboardEvents = false;
  1060. this.focusTrap = true;
  1061. this.keepContentsMounted = false;
  1062. }
  1063. onTriggerChange() {
  1064. this.configureTriggerInteraction();
  1065. }
  1066. onIsOpenChange(newValue, oldValue) {
  1067. if (newValue === true && oldValue === false) {
  1068. this.present();
  1069. }
  1070. else if (newValue === false && oldValue === true) {
  1071. this.dismiss();
  1072. }
  1073. }
  1074. connectedCallback() {
  1075. const { configureTriggerInteraction, el } = this;
  1076. overlays.prepareOverlay(el);
  1077. configureTriggerInteraction();
  1078. }
  1079. disconnectedCallback() {
  1080. const { destroyTriggerInteraction } = this;
  1081. if (destroyTriggerInteraction) {
  1082. destroyTriggerInteraction();
  1083. }
  1084. }
  1085. componentWillLoad() {
  1086. var _a, _b;
  1087. const { el } = this;
  1088. const popoverId = (_b = (_a = this.htmlAttributes) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : overlays.setOverlayId(el);
  1089. this.parentPopover = el.closest(`ion-popover:not(#${popoverId})`);
  1090. if (this.alignment === undefined) {
  1091. this.alignment = ionicGlobal.getIonMode(this) === 'ios' ? 'center' : 'start';
  1092. }
  1093. }
  1094. componentDidLoad() {
  1095. const { parentPopover, isOpen } = this;
  1096. /**
  1097. * If popover was rendered with isOpen="true"
  1098. * then we should open popover immediately.
  1099. */
  1100. if (isOpen === true) {
  1101. helpers.raf(() => this.present());
  1102. }
  1103. if (parentPopover) {
  1104. helpers.addEventListener(parentPopover, 'ionPopoverWillDismiss', () => {
  1105. this.dismiss(undefined, undefined, false);
  1106. });
  1107. }
  1108. /**
  1109. * When binding values in frameworks such as Angular
  1110. * it is possible for the value to be set after the Web Component
  1111. * initializes but before the value watcher is set up in Stencil.
  1112. * As a result, the watcher callback may not be fired.
  1113. * We work around this by manually calling the watcher
  1114. * callback when the component has loaded and the watcher
  1115. * is configured.
  1116. */
  1117. this.configureTriggerInteraction();
  1118. }
  1119. /**
  1120. * When opening a popover from a trigger, we should not be
  1121. * modifying the `event` prop from inside the component.
  1122. * Additionally, when pressing the "Right" arrow key, we need
  1123. * to shift focus to the first descendant in the newly presented
  1124. * popover.
  1125. *
  1126. * @internal
  1127. */
  1128. async presentFromTrigger(event, focusDescendant = false) {
  1129. this.focusDescendantOnPresent = focusDescendant;
  1130. await this.present(event);
  1131. this.focusDescendantOnPresent = false;
  1132. }
  1133. /**
  1134. * Determines whether or not an overlay
  1135. * is being used inline or via a controller/JS
  1136. * and returns the correct delegate.
  1137. * By default, subsequent calls to getDelegate
  1138. * will use a cached version of the delegate.
  1139. * This is useful for calling dismiss after
  1140. * present so that the correct delegate is given.
  1141. */
  1142. getDelegate(force = false) {
  1143. if (this.workingDelegate && !force) {
  1144. return {
  1145. delegate: this.workingDelegate,
  1146. inline: this.inline,
  1147. };
  1148. }
  1149. /**
  1150. * If using overlay inline
  1151. * we potentially need to use the coreDelegate
  1152. * so that this works in vanilla JS apps.
  1153. * If a developer has presented this component
  1154. * via a controller, then we can assume
  1155. * the component is already in the
  1156. * correct place.
  1157. */
  1158. const parentEl = this.el.parentNode;
  1159. const inline = (this.inline = parentEl !== null && !this.hasController);
  1160. const delegate = (this.workingDelegate = inline ? this.delegate || this.coreDelegate : this.delegate);
  1161. return { inline, delegate };
  1162. }
  1163. /**
  1164. * Present the popover overlay after it has been created.
  1165. * Developers can pass a mouse, touch, or pointer event
  1166. * to position the popover relative to where that event
  1167. * was dispatched.
  1168. */
  1169. async present(event) {
  1170. const unlock = await this.lockController.lock();
  1171. if (this.presented) {
  1172. unlock();
  1173. return;
  1174. }
  1175. const { el } = this;
  1176. const { inline, delegate } = this.getDelegate(true);
  1177. /**
  1178. * Emit ionMount so JS Frameworks have an opportunity
  1179. * to add the child component to the DOM. The child
  1180. * component will be assigned to this.usersElement below.
  1181. */
  1182. this.ionMount.emit();
  1183. this.usersElement = await frameworkDelegate.attachComponent(delegate, el, this.component, ['popover-viewport'], this.componentProps, inline);
  1184. if (!this.keyboardEvents) {
  1185. this.configureKeyboardInteraction();
  1186. }
  1187. this.configureDismissInteraction();
  1188. /**
  1189. * When using the lazy loaded build of Stencil, we need to wait
  1190. * for every Stencil component instance to be ready before presenting
  1191. * otherwise there can be a flash of unstyled content. With the
  1192. * custom elements bundle we need to wait for the JS framework
  1193. * mount the inner contents of the overlay otherwise WebKit may
  1194. * get the transition incorrect.
  1195. */
  1196. if (helpers.hasLazyBuild(el)) {
  1197. await index$2.deepReady(this.usersElement);
  1198. /**
  1199. * If keepContentsMounted="true" then the
  1200. * JS Framework has already mounted the inner
  1201. * contents so there is no need to wait.
  1202. * Otherwise, we need to wait for the JS
  1203. * Framework to mount the inner contents
  1204. * of this component.
  1205. */
  1206. }
  1207. else if (!this.keepContentsMounted) {
  1208. await index$2.waitForMount();
  1209. }
  1210. await overlays.present(this, 'popoverEnter', iosEnterAnimation, mdEnterAnimation, {
  1211. event: event || this.event,
  1212. size: this.size,
  1213. trigger: this.triggerEl,
  1214. reference: this.reference,
  1215. side: this.side,
  1216. align: this.alignment,
  1217. });
  1218. /**
  1219. * If popover is nested and was
  1220. * presented using the "Right" arrow key,
  1221. * we need to move focus to the first
  1222. * descendant inside of the popover.
  1223. */
  1224. if (this.focusDescendantOnPresent) {
  1225. overlays.focusFirstDescendant(el);
  1226. }
  1227. unlock();
  1228. }
  1229. /**
  1230. * Dismiss the popover overlay after it has been presented.
  1231. *
  1232. * @param data Any data to emit in the dismiss events.
  1233. * @param role The role of the element that is dismissing the popover. For example, 'cancel' or 'backdrop'.
  1234. * @param dismissParentPopover If `true`, dismissing this popover will also dismiss
  1235. * a parent popover if this popover is nested. Defaults to `true`.
  1236. *
  1237. * This is a no-op if the overlay has not been presented yet. If you want
  1238. * to remove an overlay from the DOM that was never presented, use the
  1239. * [remove](https://developer.mozilla.org/en-US/docs/Web/API/Element/remove) method.
  1240. */
  1241. async dismiss(data, role, dismissParentPopover = true) {
  1242. const unlock = await this.lockController.lock();
  1243. const { destroyKeyboardInteraction, destroyDismissInteraction } = this;
  1244. if (dismissParentPopover && this.parentPopover) {
  1245. this.parentPopover.dismiss(data, role, dismissParentPopover);
  1246. }
  1247. const shouldDismiss = await overlays.dismiss(this, data, role, 'popoverLeave', iosLeaveAnimation, mdLeaveAnimation, this.event);
  1248. if (shouldDismiss) {
  1249. if (destroyKeyboardInteraction) {
  1250. destroyKeyboardInteraction();
  1251. this.destroyKeyboardInteraction = undefined;
  1252. }
  1253. if (destroyDismissInteraction) {
  1254. destroyDismissInteraction();
  1255. this.destroyDismissInteraction = undefined;
  1256. }
  1257. /**
  1258. * If using popover inline
  1259. * we potentially need to use the coreDelegate
  1260. * so that this works in vanilla JS apps
  1261. */
  1262. const { delegate } = this.getDelegate();
  1263. await frameworkDelegate.detachComponent(delegate, this.usersElement);
  1264. }
  1265. unlock();
  1266. return shouldDismiss;
  1267. }
  1268. /**
  1269. * @internal
  1270. */
  1271. async getParentPopover() {
  1272. return this.parentPopover;
  1273. }
  1274. /**
  1275. * Returns a promise that resolves when the popover did dismiss.
  1276. */
  1277. onDidDismiss() {
  1278. return overlays.eventMethod(this.el, 'ionPopoverDidDismiss');
  1279. }
  1280. /**
  1281. * Returns a promise that resolves when the popover will dismiss.
  1282. */
  1283. onWillDismiss() {
  1284. return overlays.eventMethod(this.el, 'ionPopoverWillDismiss');
  1285. }
  1286. render() {
  1287. const mode = ionicGlobal.getIonMode(this);
  1288. const { onLifecycle, parentPopover, dismissOnSelect, side, arrow, htmlAttributes, focusTrap } = this;
  1289. const desktop = ionicGlobal.isPlatform('desktop');
  1290. const enableArrow = arrow && !parentPopover;
  1291. return (index.h(index.Host, Object.assign({ key: 'ff24e8d9677711248a36994cce568e74ba151499', "aria-modal": "true", "no-router": true, tabindex: "-1" }, htmlAttributes, { style: {
  1292. zIndex: `${20000 + this.overlayIndex}`,
  1293. }, class: Object.assign(Object.assign({}, theme.getClassMap(this.cssClass)), { [mode]: true, 'popover-translucent': this.translucent, 'overlay-hidden': true, 'popover-desktop': desktop, [`popover-side-${side}`]: true, [overlays.FOCUS_TRAP_DISABLE_CLASS]: focusTrap === false, 'popover-nested': !!parentPopover }), onIonPopoverDidPresent: onLifecycle, onIonPopoverWillPresent: onLifecycle, onIonPopoverWillDismiss: onLifecycle, onIonPopoverDidDismiss: onLifecycle, onIonBackdropTap: this.onBackdropTap }), !parentPopover && index.h("ion-backdrop", { key: 'aca68b4002a08b0e563a976a867141162c20f8b4', tappable: this.backdropDismiss, visible: this.showBackdrop, part: "backdrop" }), index.h("div", { key: '62d21d1eab5c6d675d49932559ffb161747e5fec', class: "popover-wrapper ion-overlay-wrapper", onClick: dismissOnSelect ? () => this.dismiss() : undefined }, enableArrow && index.h("div", { key: '1b46cc77d5302637fc979353483bb5fd780fd1d3', class: "popover-arrow", part: "arrow" }), index.h("div", { key: 'a5657bff26e46d1959b71eb0992e7dc8fcae86f1', class: "popover-content", part: "content" }, index.h("slot", { key: 'e1a98007226a46b51109e7004c4d338ca1bc0f9e' })))));
  1294. }
  1295. get el() { return index.getElement(this); }
  1296. static get watchers() { return {
  1297. "trigger": ["onTriggerChange"],
  1298. "triggerAction": ["onTriggerChange"],
  1299. "isOpen": ["onIsOpenChange"]
  1300. }; }
  1301. };
  1302. const LIFECYCLE_MAP = {
  1303. ionPopoverDidPresent: 'ionViewDidEnter',
  1304. ionPopoverWillPresent: 'ionViewWillEnter',
  1305. ionPopoverWillDismiss: 'ionViewWillLeave',
  1306. ionPopoverDidDismiss: 'ionViewDidLeave',
  1307. };
  1308. Popover.style = {
  1309. ios: IonPopoverIosStyle0,
  1310. md: IonPopoverMdStyle0
  1311. };
  1312. exports.ion_popover = Popover;