popover.js 60 KB

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