@ionic/core
Version:
Base components for Ionic
1,097 lines (1,089 loc) • 61.7 kB
JavaScript
/*!
* (C) Ionic http://ionicframework.com - MIT License
*/
import { proxyCustomElement, HTMLElement, createEvent, h, Host } from '@stencil/core/internal/client';
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';
import { C as CoreDelegate, a as attachComponent, d as detachComponent } from './framework-delegate.js';
import { r as raf, g as getElementRoot, a as addEventListener, j as hasLazyBuild } from './helpers.js';
import { c as createLockController } from './lock-controller.js';
import { p as printIonWarning } from './index6.js';
import { b as getIonMode, a as isPlatform } from './ionic-global.js';
import { g as getClassMap } from './theme.js';
import { e as deepReady, w as waitForMount } from './index2.js';
import { c as createAnimation } from './animation.js';
import { d as defineCustomElement$1 } from './backdrop.js';
/**
* Returns the dimensions of the popover
* arrow on `ios` mode. If arrow is disabled
* returns (0, 0).
*/
const getArrowDimensions = (arrowEl) => {
if (!arrowEl) {
return { arrowWidth: 0, arrowHeight: 0 };
}
const { width, height } = arrowEl.getBoundingClientRect();
return { arrowWidth: width, arrowHeight: height };
};
/**
* Returns the recommended dimensions of the popover
* that takes into account whether or not the width
* should match the trigger width.
*/
const getPopoverDimensions = (size, contentEl, triggerEl) => {
const contentDimentions = contentEl.getBoundingClientRect();
const contentHeight = contentDimentions.height;
let contentWidth = contentDimentions.width;
if (size === 'cover' && triggerEl) {
const triggerDimensions = triggerEl.getBoundingClientRect();
contentWidth = triggerDimensions.width;
}
return {
contentWidth,
contentHeight,
};
};
const configureDismissInteraction = (triggerEl, triggerAction, popoverEl, parentPopoverEl) => {
let dismissCallbacks = [];
const root = getElementRoot(parentPopoverEl);
const parentContentEl = root.querySelector('.popover-content');
switch (triggerAction) {
case 'hover':
dismissCallbacks = [
{
/**
* Do not use mouseover here
* as this will causes the event to
* be dispatched on each underlying
* element rather than on the popover
* content as a whole.
*/
eventName: 'mouseenter',
callback: (ev) => {
/**
* Do not dismiss the popover is we
* are hovering over its trigger.
* This would be easier if we used mouseover
* but this would cause the event to be dispatched
* more often than we would like, potentially
* causing performance issues.
*/
const element = document.elementFromPoint(ev.clientX, ev.clientY);
if (element === triggerEl) {
return;
}
popoverEl.dismiss(undefined, undefined, false);
},
},
];
break;
case 'context-menu':
case 'click':
default:
dismissCallbacks = [
{
eventName: 'click',
callback: (ev) => {
/**
* Do not dismiss the popover is we
* are hovering over its trigger.
*/
const target = ev.target;
const closestTrigger = target.closest('[data-ion-popover-trigger]');
if (closestTrigger === triggerEl) {
/**
* stopPropagation here so if the
* popover has dismissOnSelect="true"
* the popover does not dismiss since
* we just clicked a trigger element.
*/
ev.stopPropagation();
return;
}
popoverEl.dismiss(undefined, undefined, false);
},
},
];
break;
}
dismissCallbacks.forEach(({ eventName, callback }) => parentContentEl.addEventListener(eventName, callback));
return () => {
dismissCallbacks.forEach(({ eventName, callback }) => parentContentEl.removeEventListener(eventName, callback));
};
};
/**
* Configures the triggerEl to respond
* to user interaction based upon the triggerAction
* prop that devs have defined.
*/
const configureTriggerInteraction = (triggerEl, triggerAction, popoverEl) => {
let triggerCallbacks = [];
/**
* Based upon the kind of trigger interaction
* the user wants, we setup the correct event
* listeners.
*/
switch (triggerAction) {
case 'hover':
let hoverTimeout;
triggerCallbacks = [
{
eventName: 'mouseenter',
callback: async (ev) => {
ev.stopPropagation();
if (hoverTimeout) {
clearTimeout(hoverTimeout);
}
/**
* Hovering over a trigger should not
* immediately open the next popover.
*/
hoverTimeout = setTimeout(() => {
raf(() => {
popoverEl.presentFromTrigger(ev);
hoverTimeout = undefined;
});
}, 100);
},
},
{
eventName: 'mouseleave',
callback: (ev) => {
if (hoverTimeout) {
clearTimeout(hoverTimeout);
}
/**
* If mouse is over another popover
* that is not this popover then we should
* close this popover.
*/
const target = ev.relatedTarget;
if (!target) {
return;
}
if (target.closest('ion-popover') !== popoverEl) {
popoverEl.dismiss(undefined, undefined, false);
}
},
},
{
/**
* stopPropagation here prevents the popover
* from dismissing when dismiss-on-select="true".
*/
eventName: 'click',
callback: (ev) => ev.stopPropagation(),
},
{
eventName: 'ionPopoverActivateTrigger',
callback: (ev) => popoverEl.presentFromTrigger(ev, true),
},
];
break;
case 'context-menu':
triggerCallbacks = [
{
eventName: 'contextmenu',
callback: (ev) => {
/**
* Prevents the platform context
* menu from appearing.
*/
ev.preventDefault();
popoverEl.presentFromTrigger(ev);
},
},
{
eventName: 'click',
callback: (ev) => ev.stopPropagation(),
},
{
eventName: 'ionPopoverActivateTrigger',
callback: (ev) => popoverEl.presentFromTrigger(ev, true),
},
];
break;
case 'click':
default:
triggerCallbacks = [
{
/**
* Do not do a stopPropagation() here
* because if you had two click triggers
* then clicking the first trigger and then
* clicking the second trigger would not cause
* the first popover to dismiss.
*/
eventName: 'click',
callback: (ev) => popoverEl.presentFromTrigger(ev),
},
{
eventName: 'ionPopoverActivateTrigger',
callback: (ev) => popoverEl.presentFromTrigger(ev, true),
},
];
break;
}
triggerCallbacks.forEach(({ eventName, callback }) => triggerEl.addEventListener(eventName, callback));
triggerEl.setAttribute('data-ion-popover-trigger', 'true');
return () => {
triggerCallbacks.forEach(({ eventName, callback }) => triggerEl.removeEventListener(eventName, callback));
triggerEl.removeAttribute('data-ion-popover-trigger');
};
};
/**
* Returns the index of an ion-item in an array of ion-items.
*/
const getIndexOfItem = (items, item) => {
if (!item || item.tagName !== 'ION-ITEM') {
return -1;
}
return items.findIndex((el) => el === item);
};
/**
* Given an array of elements and a currently focused ion-item
* returns the next ion-item relative to the focused one or
* undefined.
*/
const getNextItem = (items, currentItem) => {
const currentItemIndex = getIndexOfItem(items, currentItem);
return items[currentItemIndex + 1];
};
/**
* Given an array of elements and a currently focused ion-item
* returns the previous ion-item relative to the focused one or
* undefined.
*/
const getPrevItem = (items, currentItem) => {
const currentItemIndex = getIndexOfItem(items, currentItem);
return items[currentItemIndex - 1];
};
/** Focus the internal button of the ion-item */
const focusItem = (item) => {
const root = getElementRoot(item);
const button = root.querySelector('button');
if (button) {
raf(() => button.focus());
}
};
/**
* Returns `true` if `el` has been designated
* as a trigger element for an ion-popover.
*/
const isTriggerElement = (el) => el.hasAttribute('data-ion-popover-trigger');
const configureKeyboardInteraction = (popoverEl) => {
const callback = async (ev) => {
var _a;
const activeElement = document.activeElement;
let items = [];
const targetTagName = (_a = ev.target) === null || _a === void 0 ? void 0 : _a.tagName;
/**
* Only handle custom keyboard interactions for the host popover element
* and children ion-item elements.
*/
if (targetTagName !== 'ION-POPOVER' && targetTagName !== 'ION-ITEM') {
return;
}
/**
* Complex selectors with :not() are :not supported
* in older versions of Chromium so we need to do a
* try/catch here so errors are not thrown.
*/
try {
/**
* Select all ion-items that are not children of child popovers.
* i.e. only select ion-item elements that are part of this popover
*/
items = Array.from(popoverEl.querySelectorAll('ion-item:not(ion-popover ion-popover *):not([disabled])'));
/* eslint-disable-next-line */
}
catch (_b) { }
switch (ev.key) {
/**
* If we are in a child popover
* then pressing the left arrow key
* should close this popover and move
* focus to the popover that presented
* this one.
*/
case 'ArrowLeft':
const parentPopover = await popoverEl.getParentPopover();
if (parentPopover) {
popoverEl.dismiss(undefined, undefined, false);
}
break;
/**
* ArrowDown should move focus to the next focusable ion-item.
*/
case 'ArrowDown':
// Disable movement/scroll with keyboard
ev.preventDefault();
const nextItem = getNextItem(items, activeElement);
if (nextItem !== undefined) {
focusItem(nextItem);
}
break;
/**
* ArrowUp should move focus to the previous focusable ion-item.
*/
case 'ArrowUp':
// Disable movement/scroll with keyboard
ev.preventDefault();
const prevItem = getPrevItem(items, activeElement);
if (prevItem !== undefined) {
focusItem(prevItem);
}
break;
/**
* Home should move focus to the first focusable ion-item.
*/
case 'Home':
ev.preventDefault();
const firstItem = items[0];
if (firstItem !== undefined) {
focusItem(firstItem);
}
break;
/**
* End should move focus to the last focusable ion-item.
*/
case 'End':
ev.preventDefault();
const lastItem = items[items.length - 1];
if (lastItem !== undefined) {
focusItem(lastItem);
}
break;
/**
* ArrowRight, Spacebar, or Enter should activate
* the currently focused trigger item to open a
* popover if the element is a trigger item.
*/
case 'ArrowRight':
case ' ':
case 'Enter':
if (activeElement && isTriggerElement(activeElement)) {
const rightEvent = new CustomEvent('ionPopoverActivateTrigger');
activeElement.dispatchEvent(rightEvent);
}
break;
}
};
popoverEl.addEventListener('keydown', callback);
return () => popoverEl.removeEventListener('keydown', callback);
};
/**
* Positions a popover by taking into account
* the reference point, preferred side, alignment
* and viewport dimensions.
*/
const getPopoverPosition = (isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, triggerEl, event) => {
var _a;
let referenceCoordinates = {
top: 0,
left: 0,
width: 0,
height: 0,
};
/**
* Calculate position relative to the
* x-y coordinates in the event that
* was passed in
*/
switch (reference) {
case 'event':
if (!event) {
return defaultPosition;
}
const mouseEv = event;
referenceCoordinates = {
top: mouseEv.clientY,
left: mouseEv.clientX,
width: 1,
height: 1,
};
break;
/**
* Calculate position relative to the bounding
* box on either the trigger element
* specified via the `trigger` prop or
* the target specified on the event
* that was passed in.
*/
case 'trigger':
default:
const customEv = event;
/**
* ionShadowTarget is used when we need to align the
* popover with an element inside of the shadow root
* of an Ionic component. Ex: Presenting a popover
* by clicking on the collapsed indicator inside
* of `ion-breadcrumb` and centering it relative
* to the indicator rather than `ion-breadcrumb`
* as a whole.
*/
const actualTriggerEl = (triggerEl ||
((_a = customEv === null || customEv === void 0 ? void 0 : customEv.detail) === null || _a === void 0 ? void 0 : _a.ionShadowTarget) ||
(customEv === null || customEv === void 0 ? void 0 : customEv.target));
if (!actualTriggerEl) {
return defaultPosition;
}
const triggerBoundingBox = actualTriggerEl.getBoundingClientRect();
referenceCoordinates = {
top: triggerBoundingBox.top,
left: triggerBoundingBox.left,
width: triggerBoundingBox.width,
height: triggerBoundingBox.height,
};
break;
}
/**
* Get top/left offset that would allow
* popover to be positioned on the
* preferred side of the reference.
*/
const coordinates = calculatePopoverSide(side, referenceCoordinates, contentWidth, contentHeight, arrowWidth, arrowHeight, isRTL);
/**
* Get the top/left adjustments that
* would allow the popover content
* to have the correct alignment.
*/
const alignedCoordinates = calculatePopoverAlign(align, side, referenceCoordinates, contentWidth, contentHeight);
const top = coordinates.top + alignedCoordinates.top;
const left = coordinates.left + alignedCoordinates.left;
const { arrowTop, arrowLeft } = calculateArrowPosition(side, arrowWidth, arrowHeight, top, left, contentWidth, contentHeight, isRTL);
const { originX, originY } = calculatePopoverOrigin(side, align, isRTL);
return { top, left, referenceCoordinates, arrowTop, arrowLeft, originX, originY };
};
/**
* Determines the transform-origin
* of the popover animation so that it
* is in line with what the side and alignment
* prop values are. Currently only used
* with the MD animation.
*/
const calculatePopoverOrigin = (side, align, isRTL) => {
switch (side) {
case 'top':
return { originX: getOriginXAlignment(align), originY: 'bottom' };
case 'bottom':
return { originX: getOriginXAlignment(align), originY: 'top' };
case 'left':
return { originX: 'right', originY: getOriginYAlignment(align) };
case 'right':
return { originX: 'left', originY: getOriginYAlignment(align) };
case 'start':
return { originX: isRTL ? 'left' : 'right', originY: getOriginYAlignment(align) };
case 'end':
return { originX: isRTL ? 'right' : 'left', originY: getOriginYAlignment(align) };
}
};
const getOriginXAlignment = (align) => {
switch (align) {
case 'start':
return 'left';
case 'center':
return 'center';
case 'end':
return 'right';
}
};
const getOriginYAlignment = (align) => {
switch (align) {
case 'start':
return 'top';
case 'center':
return 'center';
case 'end':
return 'bottom';
}
};
/**
* Calculates where the arrow positioning
* should be relative to the popover content.
*/
const calculateArrowPosition = (side, arrowWidth, arrowHeight, top, left, contentWidth, contentHeight, isRTL) => {
/**
* Note: When side is left, right, start, or end, the arrow is
* been rotated using a `transform`, so to move the arrow up or down
* by its dimension, you need to use `arrowWidth`.
*/
const leftPosition = {
arrowTop: top + contentHeight / 2 - arrowWidth / 2,
arrowLeft: left + contentWidth - arrowWidth / 2,
};
/**
* Move the arrow to the left by arrowWidth and then
* again by half of its width because we have rotated
* the arrow using a transform.
*/
const rightPosition = { arrowTop: top + contentHeight / 2 - arrowWidth / 2, arrowLeft: left - arrowWidth * 1.5 };
switch (side) {
case 'top':
return { arrowTop: top + contentHeight, arrowLeft: left + contentWidth / 2 - arrowWidth / 2 };
case 'bottom':
return { arrowTop: top - arrowHeight, arrowLeft: left + contentWidth / 2 - arrowWidth / 2 };
case 'left':
return leftPosition;
case 'right':
return rightPosition;
case 'start':
return isRTL ? rightPosition : leftPosition;
case 'end':
return isRTL ? leftPosition : rightPosition;
default:
return { arrowTop: 0, arrowLeft: 0 };
}
};
/**
* Calculates the required top/left
* values needed to position the popover
* content on the side specified in the
* `side` prop.
*/
const calculatePopoverSide = (side, triggerBoundingBox, contentWidth, contentHeight, arrowWidth, arrowHeight, isRTL) => {
const sideLeft = {
top: triggerBoundingBox.top,
left: triggerBoundingBox.left - contentWidth - arrowWidth,
};
const sideRight = {
top: triggerBoundingBox.top,
left: triggerBoundingBox.left + triggerBoundingBox.width + arrowWidth,
};
switch (side) {
case 'top':
return {
top: triggerBoundingBox.top - contentHeight - arrowHeight,
left: triggerBoundingBox.left,
};
case 'right':
return sideRight;
case 'bottom':
return {
top: triggerBoundingBox.top + triggerBoundingBox.height + arrowHeight,
left: triggerBoundingBox.left,
};
case 'left':
return sideLeft;
case 'start':
return isRTL ? sideRight : sideLeft;
case 'end':
return isRTL ? sideLeft : sideRight;
}
};
/**
* Calculates the required top/left
* offset values needed to provide the
* correct alignment regardless while taking
* into account the side the popover is on.
*/
const calculatePopoverAlign = (align, side, triggerBoundingBox, contentWidth, contentHeight) => {
switch (align) {
case 'center':
return calculatePopoverCenterAlign(side, triggerBoundingBox, contentWidth, contentHeight);
case 'end':
return calculatePopoverEndAlign(side, triggerBoundingBox, contentWidth, contentHeight);
case 'start':
default:
return { top: 0, left: 0 };
}
};
/**
* Calculate the end alignment for
* the popover. If side is on the x-axis
* then the align values refer to the top
* and bottom margins of the content.
* If side is on the y-axis then the
* align values refer to the left and right
* margins of the content.
*/
const calculatePopoverEndAlign = (side, triggerBoundingBox, contentWidth, contentHeight) => {
switch (side) {
case 'start':
case 'end':
case 'left':
case 'right':
return {
top: -(contentHeight - triggerBoundingBox.height),
left: 0,
};
case 'top':
case 'bottom':
default:
return {
top: 0,
left: -(contentWidth - triggerBoundingBox.width),
};
}
};
/**
* Calculate the center alignment for
* the popover. If side is on the x-axis
* then the align values refer to the top
* and bottom margins of the content.
* If side is on the y-axis then the
* align values refer to the left and right
* margins of the content.
*/
const calculatePopoverCenterAlign = (side, triggerBoundingBox, contentWidth, contentHeight) => {
switch (side) {
case 'start':
case 'end':
case 'left':
case 'right':
return {
top: -(contentHeight / 2 - triggerBoundingBox.height / 2),
left: 0,
};
case 'top':
case 'bottom':
default:
return {
top: 0,
left: -(contentWidth / 2 - triggerBoundingBox.width / 2),
};
}
};
/**
* Adjusts popover positioning coordinates
* such that popover does not appear offscreen
* or overlapping safe area bounds.
*/
const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeAreaMargin, contentOriginX, contentOriginY, triggerCoordinates, coordArrowTop = 0, coordArrowLeft = 0, arrowHeight = 0) => {
let arrowTop = coordArrowTop;
const arrowLeft = coordArrowLeft;
let left = coordLeft;
let top = coordTop;
let bottom;
let originX = contentOriginX;
let originY = contentOriginY;
let checkSafeAreaLeft = false;
let checkSafeAreaRight = false;
const triggerTop = triggerCoordinates
? triggerCoordinates.top + triggerCoordinates.height
: bodyHeight / 2 - contentHeight / 2;
const triggerHeight = triggerCoordinates ? triggerCoordinates.height : 0;
let addPopoverBottomClass = false;
/**
* Adjust popover so it does not
* go off the left of the screen.
*/
if (left < bodyPadding + safeAreaMargin) {
left = bodyPadding;
checkSafeAreaLeft = true;
originX = 'left';
/**
* Adjust popover so it does not
* go off the right of the screen.
*/
}
else if (contentWidth + bodyPadding + left + safeAreaMargin > bodyWidth) {
checkSafeAreaRight = true;
left = bodyWidth - contentWidth - bodyPadding;
originX = 'right';
}
/**
* Adjust popover so it does not
* go off the top of the screen.
* If popover is on the left or the right of
* the trigger, then we should not adjust top
* margins.
*/
if (triggerTop + triggerHeight + contentHeight > bodyHeight && (side === 'top' || side === 'bottom')) {
if (triggerTop - contentHeight > 0) {
/**
* While we strive to align the popover with the trigger
* on smaller screens this is not always possible. As a result,
* we adjust the popover up so that it does not hang
* off the bottom of the screen. However, we do not want to move
* the popover up so much that it goes off the top of the screen.
*
* We chose 12 here so that the popover position looks a bit nicer as
* it is not right up against the edge of the screen.
*/
top = Math.max(12, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1));
arrowTop = top + contentHeight;
originY = 'bottom';
addPopoverBottomClass = true;
/**
* If not enough room for popover to appear
* above trigger, then cut it off.
*/
}
else {
bottom = bodyPadding;
}
}
return {
top,
left,
bottom,
originX,
originY,
checkSafeAreaLeft,
checkSafeAreaRight,
arrowTop,
arrowLeft,
addPopoverBottomClass,
};
};
const shouldShowArrow = (side, didAdjustBounds = false, ev, trigger) => {
/**
* If no event provided and
* we do not have a trigger,
* then this popover was likely
* presented via the popoverController
* or users called `present` manually.
* In this case, the arrow should not be
* shown as we do not have a reference.
*/
if (!ev && !trigger) {
return false;
}
/**
* If popover is on the left or the right
* of a trigger, but we needed to adjust the
* popover due to screen bounds, then we should
* hide the arrow as it will never be pointing
* at the trigger.
*/
if (side !== 'top' && side !== 'bottom' && didAdjustBounds) {
return false;
}
return true;
};
const POPOVER_IOS_BODY_PADDING = 5;
/**
* iOS Popover Enter Animation
*/
// TODO(FW-2832): types
const iosEnterAnimation = (baseEl, opts) => {
var _a;
const { event: ev, size, trigger, reference, side, align } = opts;
const doc = baseEl.ownerDocument;
const isRTL = doc.dir === 'rtl';
const bodyWidth = doc.defaultView.innerWidth;
const bodyHeight = doc.defaultView.innerHeight;
const root = getElementRoot(baseEl);
const contentEl = root.querySelector('.popover-content');
const arrowEl = root.querySelector('.popover-arrow');
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);
const { contentWidth, contentHeight } = getPopoverDimensions(size, contentEl, referenceSizeEl);
const { arrowWidth, arrowHeight } = getArrowDimensions(arrowEl);
const defaultPosition = {
top: bodyHeight / 2 - contentHeight / 2,
left: bodyWidth / 2 - contentWidth / 2,
originX: isRTL ? 'right' : 'left',
originY: 'top',
};
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, trigger, ev);
const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
const margin = size === 'cover' ? 0 : 25;
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);
const baseAnimation = createAnimation();
const backdropAnimation = createAnimation();
const contentAnimation = createAnimation();
backdropAnimation
.addElement(root.querySelector('ion-backdrop'))
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
.beforeStyles({
'pointer-events': 'none',
})
.afterClearStyles(['pointer-events']);
// In Chromium, if the wrapper animates, the backdrop filter doesn't work.
// 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.
// To get around this, instead of animating the wrapper, animate both the arrow and content.
// https://bugs.chromium.org/p/chromium/issues/detail?id=1148826
contentAnimation
.addElement(root.querySelector('.popover-arrow'))
.addElement(root.querySelector('.popover-content'))
.fromTo('opacity', 0.01, 1);
// TODO(FW-4376) Ensure that arrow also blurs when translucent
return baseAnimation
.easing('ease')
.duration(100)
.beforeAddWrite(() => {
if (size === 'cover') {
baseEl.style.setProperty('--width', `${contentWidth}px`);
}
if (addPopoverBottomClass) {
baseEl.classList.add('popover-bottom');
}
if (bottom !== undefined) {
contentEl.style.setProperty('bottom', `${bottom}px`);
}
const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
let leftValue = `${left}px`;
if (checkSafeAreaLeft) {
leftValue = `${left}px${safeAreaLeft}`;
}
if (checkSafeAreaRight) {
leftValue = `${left}px${safeAreaRight}`;
}
contentEl.style.setProperty('top', `calc(${top}px + var(--offset-y, 0))`);
contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
if (arrowEl !== null) {
const didAdjustBounds = results.top !== top || results.left !== left;
const showArrow = shouldShowArrow(side, didAdjustBounds, ev, trigger);
if (showArrow) {
arrowEl.style.setProperty('top', `calc(${arrowTop}px + var(--offset-y, 0))`);
arrowEl.style.setProperty('left', `calc(${arrowLeft}px + var(--offset-x, 0))`);
}
else {
arrowEl.style.setProperty('display', 'none');
}
}
})
.addAnimation([backdropAnimation, contentAnimation]);
};
/**
* iOS Popover Leave Animation
*/
const iosLeaveAnimation = (baseEl) => {
const root = getElementRoot(baseEl);
const contentEl = root.querySelector('.popover-content');
const arrowEl = root.querySelector('.popover-arrow');
const baseAnimation = createAnimation();
const backdropAnimation = createAnimation();
const contentAnimation = createAnimation();
backdropAnimation.addElement(root.querySelector('ion-backdrop')).fromTo('opacity', 'var(--backdrop-opacity)', 0);
contentAnimation
.addElement(root.querySelector('.popover-arrow'))
.addElement(root.querySelector('.popover-content'))
.fromTo('opacity', 0.99, 0);
return baseAnimation
.easing('ease')
.afterAddWrite(() => {
baseEl.style.removeProperty('--width');
baseEl.classList.remove('popover-bottom');
contentEl.style.removeProperty('top');
contentEl.style.removeProperty('left');
contentEl.style.removeProperty('bottom');
contentEl.style.removeProperty('transform-origin');
if (arrowEl) {
arrowEl.style.removeProperty('top');
arrowEl.style.removeProperty('left');
arrowEl.style.removeProperty('display');
}
})
.duration(300)
.addAnimation([backdropAnimation, contentAnimation]);
};
const POPOVER_MD_BODY_PADDING = 12;
/**
* Md Popover Enter Animation
*/
// TODO(FW-2832): types
const mdEnterAnimation = (baseEl, opts) => {
var _a;
const { event: ev, size, trigger, reference, side, align } = opts;
const doc = baseEl.ownerDocument;
const isRTL = doc.dir === 'rtl';
const bodyWidth = doc.defaultView.innerWidth;
const bodyHeight = doc.defaultView.innerHeight;
const root = getElementRoot(baseEl);
const contentEl = root.querySelector('.popover-content');
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);
const { contentWidth, contentHeight } = getPopoverDimensions(size, contentEl, referenceSizeEl);
const defaultPosition = {
top: bodyHeight / 2 - contentHeight / 2,
left: bodyWidth / 2 - contentWidth / 2,
originX: isRTL ? 'right' : 'left',
originY: 'top',
};
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, 0, 0, reference, side, align, defaultPosition, trigger, ev);
const padding = size === 'cover' ? 0 : POPOVER_MD_BODY_PADDING;
const { originX, originY, top, left, bottom } = calculateWindowAdjustment(side, results.top, results.left, padding, bodyWidth, bodyHeight, contentWidth, contentHeight, 0, results.originX, results.originY, results.referenceCoordinates);
const baseAnimation = createAnimation();
const backdropAnimation = createAnimation();
const wrapperAnimation = createAnimation();
const contentAnimation = createAnimation();
const viewportAnimation = createAnimation();
backdropAnimation
.addElement(root.querySelector('ion-backdrop'))
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
.beforeStyles({
'pointer-events': 'none',
})
.afterClearStyles(['pointer-events']);
wrapperAnimation.addElement(root.querySelector('.popover-wrapper')).duration(150).fromTo('opacity', 0.01, 1);
contentAnimation
.addElement(contentEl)
.beforeStyles({
top: `calc(${top}px + var(--offset-y, 0px))`,
left: `calc(${left}px + var(--offset-x, 0px))`,
'transform-origin': `${originY} ${originX}`,
})
.beforeAddWrite(() => {
if (bottom !== undefined) {
contentEl.style.setProperty('bottom', `${bottom}px`);
}
})
.fromTo('transform', 'scale(0.8)', 'scale(1)');
viewportAnimation.addElement(root.querySelector('.popover-viewport')).fromTo('opacity', 0.01, 1);
return baseAnimation
.easing('cubic-bezier(0.36,0.66,0.04,1)')
.duration(300)
.beforeAddWrite(() => {
if (size === 'cover') {
baseEl.style.setProperty('--width', `${contentWidth}px`);
}
if (originY === 'bottom') {
baseEl.classList.add('popover-bottom');
}
})
.addAnimation([backdropAnimation, wrapperAnimation, contentAnimation, viewportAnimation]);
};
/**
* Md Popover Leave Animation
*/
const mdLeaveAnimation = (baseEl) => {
const root = getElementRoot(baseEl);
const contentEl = root.querySelector('.popover-content');
const baseAnimation = createAnimation();
const backdropAnimation = createAnimation();
const wrapperAnimation = createAnimation();
backdropAnimation.addElement(root.querySelector('ion-backdrop')).fromTo('opacity', 'var(--backdrop-opacity)', 0);
wrapperAnimation.addElement(root.querySelector('.popover-wrapper')).fromTo('opacity', 0.99, 0);
return baseAnimation
.easing('ease')
.afterAddWrite(() => {
baseEl.style.removeProperty('--width');
baseEl.classList.remove('popover-bottom');
contentEl.style.removeProperty('top');
contentEl.style.removeProperty('left');
contentEl.style.removeProperty('bottom');
contentEl.style.removeProperty('transform-origin');
})
.duration(150)
.addAnimation([backdropAnimation, wrapperAnimation]);
};
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)}}";
const IonPopoverIosStyle0 = popoverIosCss;
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}";
const IonPopoverMdStyle0 = popoverMdCss;
const Popover = /*@__PURE__*/ proxyCustomElement(class Popover extends HTMLElement {
constructor() {
super();
this.__registerHost();
this.__attachShadow();
this.didPresent = createEvent(this, "ionPopoverDidPresent", 7);
this.willPresent = createEvent(this, "ionPopoverWillPresent", 7);
this.willDismiss = createEvent(this, "ionPopoverWillDismiss", 7);
this.didDismiss = createEvent(this, "ionPopoverDidDismiss", 7);
this.didPresentShorthand = createEvent(this, "didPresent", 7);
this.willPresentShorthand = createEvent(this, "willPresent", 7);
this.willDismissShorthand = createEvent(this, "willDismiss", 7);
this.didDismissShorthand = createEvent(this, "didDismiss", 7);
this.ionMount = createEvent(this, "ionMount", 7);
this.parentPopover = null;
this.coreDelegate = CoreDelegate();
this.lockController = createLockController();
this.inline = false;
this.focusDescendantOnPresent = false;
this.onBackdropTap = () => {
this.dismiss(undefined, BACKDROP);
};
this.onLifecycle = (modalEvent) => {
const el = this.usersElement;
const name = LIFECYCLE_MAP[modalEvent.type];
if (el && name) {
const event = new CustomEvent(name, {
bubbles: false,
cancelable: false,
detail: modalEvent.detail,
});
el.dispatchEvent(event);
}
};
this.configureTriggerInteraction = () => {
const { trigger, triggerAction, el, destroyTriggerInteraction } = this;
if (destroyTriggerInteraction) {
destroyTriggerInteraction();
}
if (trigger === undefined) {
return;
}
const triggerEl = (this.triggerEl = trigger !== undefined ? document.getElementById(trigger) : null);
if (!triggerEl) {
printIonWarning(`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);
return;
}
this.destroyTriggerInteraction = configureTriggerInteraction(triggerEl, triggerAction, el);
};
this.configureKeyboardInteraction = () => {
const { destroyKeyboardInteraction, el } = this;
if (destroyKeyboardInteraction) {
destroyKeyboardInteraction();
}
this.destroyKeyboardInteraction = configureKeyboardInteraction(el);
};
this.configureDismissInteraction = () => {
const { destroyDismissInteraction, parentPopover, triggerAction, triggerEl, el } = this;
if (!parentPopover || !triggerEl) {
return;
}
if (destroyDismissInteraction) {
destroyDismissInteraction();
}
this.destroyDismissInteraction = configureDismissInteraction(triggerEl, triggerAction, el, parentPopover);
};
this.presented = false;
this.hasController = false;
this.delegate = undefined;
this.overlayIndex = undefined;
this.enterAnimation = undefined;
this.leaveAnimation = undefined;
this.component = undefined;
this.componentProps = undefined;
this.keyboardClose = true;
this.cssClass = undefined;
this.backdropDismiss = true;
this.event = undefined;
this.showBackdrop = true;
this.translucent = false;
this.animated = true;
this.htmlAttributes = undefined;
this.triggerAction = 'click';
this.trigger = undefined;
this.size = 'auto';
this.dismissOnSelect = false;
this.reference = 'trigger';
this.side = 'bottom';
this.alignment = undefined;
this.arrow = true;
this.isOpen = false;
this.keyboardEvents = false;
this.focusTrap = true;
this.keepContentsMounted = false;
}
onTriggerChange() {
this.configureTriggerInteraction();
}
onIsOpenChange(newValue, oldValue) {
if (newValue === true && oldValue === false) {
this.present();
}
else if (newValue === false && oldValue === true) {
this.dismiss();
}
}
connectedCallback() {
const { configureTriggerInteraction, el } = this;
prepareOverlay(el);
configureTriggerInteraction();
}
disconnectedCallback() {
const { destroyTriggerInteraction } = this;
if (destroyTriggerInteraction) {
destroyTriggerInteraction();
}
}
componentWillLoad() {
var _a, _b;
const { el } = this;
const popoverId = (_b = (_a = this.htmlAttributes) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : setOverlayId(el);
this.parentPopover = el.closest(`ion-popover:not(#${popoverId})`);
if (this.alignment === undefined) {
this.alignment = getIonMode(this) === 'ios' ? 'cent