@synergy-design-system/components
Version:
This package provides the base of the Synergy Design System as native web components. It uses [lit](https://www.lit.dev) and parts of [shoelace](https://shoelace.style/). Synergy officially supports the latest two versions of all major browsers (as define
419 lines (414 loc) • 14.2 kB
JavaScript
import {
popup_styles_default
} from "./chunk.AZ3N5IOO.js";
import {
LocalizeController
} from "./chunk.OAQRCZOO.js";
import {
component_styles_default
} from "./chunk.NLYVOJGK.js";
import {
SynergyElement
} from "./chunk.3AZFEB6D.js";
import {
__decorateClass,
__spreadProps,
__spreadValues
} from "./chunk.Z4XV3SMG.js";
// src/components/popup/popup.component.ts
import { arrow, autoUpdate, computePosition, flip, offset, platform, shift, size } from "@floating-ui/dom";
import { classMap } from "lit/directives/class-map.js";
import { html } from "lit";
import { offsetParent } from "composed-offset-position";
import { property, query } from "lit/decorators.js";
function isVirtualElement(e) {
return e !== null && typeof e === "object" && "getBoundingClientRect" in e && ("contextElement" in e ? e.contextElement instanceof Element : true);
}
var SynPopup = class extends SynergyElement {
constructor() {
super(...arguments);
this.localize = new LocalizeController(this);
this.active = false;
this.placement = "top";
this.strategy = "absolute";
this.distance = 0;
this.skidding = 0;
this.arrow = false;
this.arrowPlacement = "anchor";
this.arrowPadding = 10;
this.flip = false;
this.flipFallbackPlacements = "";
this.flipFallbackStrategy = "best-fit";
this.flipPadding = 0;
this.shift = false;
this.shiftPadding = 0;
this.autoSizePadding = 0;
this.hoverBridge = false;
this.updateHoverBridge = () => {
if (this.hoverBridge && this.anchorEl) {
const anchorRect = this.anchorEl.getBoundingClientRect();
const popupRect = this.popup.getBoundingClientRect();
const isVertical = this.placement.includes("top") || this.placement.includes("bottom");
let topLeftX = 0;
let topLeftY = 0;
let topRightX = 0;
let topRightY = 0;
let bottomLeftX = 0;
let bottomLeftY = 0;
let bottomRightX = 0;
let bottomRightY = 0;
if (isVertical) {
if (anchorRect.top < popupRect.top) {
topLeftX = anchorRect.left;
topLeftY = anchorRect.bottom;
topRightX = anchorRect.right;
topRightY = anchorRect.bottom;
bottomLeftX = popupRect.left;
bottomLeftY = popupRect.top;
bottomRightX = popupRect.right;
bottomRightY = popupRect.top;
} else {
topLeftX = popupRect.left;
topLeftY = popupRect.bottom;
topRightX = popupRect.right;
topRightY = popupRect.bottom;
bottomLeftX = anchorRect.left;
bottomLeftY = anchorRect.top;
bottomRightX = anchorRect.right;
bottomRightY = anchorRect.top;
}
} else {
if (anchorRect.left < popupRect.left) {
topLeftX = anchorRect.right;
topLeftY = anchorRect.top;
topRightX = popupRect.left;
topRightY = popupRect.top;
bottomLeftX = anchorRect.right;
bottomLeftY = anchorRect.bottom;
bottomRightX = popupRect.left;
bottomRightY = popupRect.bottom;
} else {
topLeftX = popupRect.right;
topLeftY = popupRect.top;
topRightX = anchorRect.left;
topRightY = anchorRect.top;
bottomLeftX = popupRect.right;
bottomLeftY = popupRect.bottom;
bottomRightX = anchorRect.left;
bottomRightY = anchorRect.bottom;
}
}
this.style.setProperty("--hover-bridge-top-left-x", `${topLeftX}px`);
this.style.setProperty("--hover-bridge-top-left-y", `${topLeftY}px`);
this.style.setProperty("--hover-bridge-top-right-x", `${topRightX}px`);
this.style.setProperty("--hover-bridge-top-right-y", `${topRightY}px`);
this.style.setProperty("--hover-bridge-bottom-left-x", `${bottomLeftX}px`);
this.style.setProperty("--hover-bridge-bottom-left-y", `${bottomLeftY}px`);
this.style.setProperty("--hover-bridge-bottom-right-x", `${bottomRightX}px`);
this.style.setProperty("--hover-bridge-bottom-right-y", `${bottomRightY}px`);
}
};
}
async connectedCallback() {
super.connectedCallback();
await this.updateComplete;
this.start();
}
disconnectedCallback() {
super.disconnectedCallback();
this.stop();
}
async updated(changedProps) {
super.updated(changedProps);
if (changedProps.has("active")) {
if (this.active) {
this.start();
} else {
this.stop();
}
}
if (changedProps.has("anchor")) {
this.handleAnchorChange();
}
if (this.active) {
await this.updateComplete;
this.reposition();
}
}
async handleAnchorChange() {
await this.stop();
if (this.anchor && typeof this.anchor === "string") {
const root = this.getRootNode();
this.anchorEl = root.getElementById(this.anchor);
} else if (this.anchor instanceof Element || isVirtualElement(this.anchor)) {
this.anchorEl = this.anchor;
} else {
this.anchorEl = this.querySelector('[slot="anchor"]');
}
if (this.anchorEl instanceof HTMLSlotElement) {
this.anchorEl = this.anchorEl.assignedElements({ flatten: true })[0];
}
if (this.anchorEl && this.active) {
this.start();
}
}
start() {
if (!this.anchorEl || !this.active) {
return;
}
this.cleanup = autoUpdate(this.anchorEl, this.popup, () => {
this.reposition();
});
}
async stop() {
return new Promise((resolve) => {
if (this.cleanup) {
this.cleanup();
this.cleanup = void 0;
this.removeAttribute("data-current-placement");
this.style.removeProperty("--auto-size-available-width");
this.style.removeProperty("--auto-size-available-height");
requestAnimationFrame(() => resolve());
} else {
resolve();
}
});
}
/** Forces the popup to recalculate and reposition itself. */
reposition() {
if (!this.active || !this.anchorEl) {
return;
}
const middleware = [
// The offset middleware goes first
offset({ mainAxis: this.distance, crossAxis: this.skidding })
];
if (this.sync) {
middleware.push(
size({
apply: ({ rects }) => {
const syncWidth = this.sync === "width" || this.sync === "both";
const syncHeight = this.sync === "height" || this.sync === "both";
this.popup.style.width = syncWidth ? `${rects.reference.width}px` : "";
this.popup.style.height = syncHeight ? `${rects.reference.height}px` : "";
}
})
);
} else {
this.popup.style.width = "";
this.popup.style.height = "";
}
if (this.flip) {
middleware.push(
flip({
boundary: this.flipBoundary,
// @ts-expect-error - We're converting a string attribute to an array here
fallbackPlacements: this.flipFallbackPlacements,
fallbackStrategy: this.flipFallbackStrategy === "best-fit" ? "bestFit" : "initialPlacement",
padding: this.flipPadding
})
);
}
if (this.shift) {
middleware.push(
shift({
boundary: this.shiftBoundary,
padding: this.shiftPadding
})
);
}
if (this.autoSize) {
middleware.push(
size({
boundary: this.autoSizeBoundary,
padding: this.autoSizePadding,
apply: ({ availableWidth, availableHeight }) => {
if (this.autoSize === "vertical" || this.autoSize === "both") {
this.style.setProperty("--auto-size-available-height", `${availableHeight}px`);
} else {
this.style.removeProperty("--auto-size-available-height");
}
if (this.autoSize === "horizontal" || this.autoSize === "both") {
this.style.setProperty("--auto-size-available-width", `${availableWidth}px`);
} else {
this.style.removeProperty("--auto-size-available-width");
}
}
})
);
} else {
this.style.removeProperty("--auto-size-available-width");
this.style.removeProperty("--auto-size-available-height");
}
if (this.arrow) {
middleware.push(
arrow({
element: this.arrowEl,
padding: this.arrowPadding
})
);
}
const getOffsetParent = this.strategy === "absolute" ? (element) => platform.getOffsetParent(element, offsetParent) : platform.getOffsetParent;
computePosition(this.anchorEl, this.popup, {
placement: this.placement,
middleware,
strategy: this.strategy,
platform: __spreadProps(__spreadValues({}, platform), {
getOffsetParent
})
}).then(({ x, y, middlewareData, placement }) => {
const isRtl = this.localize.dir() === "rtl";
const staticSide = { top: "bottom", right: "left", bottom: "top", left: "right" }[placement.split("-")[0]];
this.setAttribute("data-current-placement", placement);
Object.assign(this.popup.style, {
left: `${x}px`,
top: `${y}px`
});
if (this.arrow) {
const arrowX = middlewareData.arrow.x;
const arrowY = middlewareData.arrow.y;
let top = "";
let right = "";
let bottom = "";
let left = "";
if (this.arrowPlacement === "start") {
const value = typeof arrowX === "number" ? `calc(${this.arrowPadding}px - var(--arrow-padding-offset))` : "";
top = typeof arrowY === "number" ? `calc(${this.arrowPadding}px - var(--arrow-padding-offset))` : "";
right = isRtl ? value : "";
left = isRtl ? "" : value;
} else if (this.arrowPlacement === "end") {
const value = typeof arrowX === "number" ? `calc(${this.arrowPadding}px - var(--arrow-padding-offset))` : "";
right = isRtl ? "" : value;
left = isRtl ? value : "";
bottom = typeof arrowY === "number" ? `calc(${this.arrowPadding}px - var(--arrow-padding-offset))` : "";
} else if (this.arrowPlacement === "center") {
left = typeof arrowX === "number" ? `calc(50% - var(--arrow-size-diagonal))` : "";
top = typeof arrowY === "number" ? `calc(50% - var(--arrow-size-diagonal))` : "";
} else {
left = typeof arrowX === "number" ? `${arrowX}px` : "";
top = typeof arrowY === "number" ? `${arrowY}px` : "";
}
Object.assign(this.arrowEl.style, {
top,
right,
bottom,
left,
[staticSide]: "calc(var(--arrow-size-diagonal) * -1)"
});
}
});
requestAnimationFrame(() => this.updateHoverBridge());
this.emit("syn-reposition");
}
render() {
return html`
<slot name="anchor" =${this.handleAnchorChange}></slot>
<span
part="hover-bridge"
class=${classMap({
"popup-hover-bridge": true,
"popup-hover-bridge--visible": this.hoverBridge && this.active
})}
></span>
<div
part="popup"
class=${classMap({
popup: true,
"popup--active": this.active,
"popup--fixed": this.strategy === "fixed",
"popup--has-arrow": this.arrow
})}
>
<slot></slot>
${this.arrow ? html`<div part="arrow" class="popup__arrow" role="presentation"></div>` : ""}
</div>
`;
}
};
SynPopup.styles = [component_styles_default, popup_styles_default];
__decorateClass([
query(".popup")
], SynPopup.prototype, "popup", 2);
__decorateClass([
query(".popup__arrow")
], SynPopup.prototype, "arrowEl", 2);
__decorateClass([
property()
], SynPopup.prototype, "anchor", 2);
__decorateClass([
property({ type: Boolean, reflect: true })
], SynPopup.prototype, "active", 2);
__decorateClass([
property({ reflect: true })
], SynPopup.prototype, "placement", 2);
__decorateClass([
property({ reflect: true })
], SynPopup.prototype, "strategy", 2);
__decorateClass([
property({ type: Number })
], SynPopup.prototype, "distance", 2);
__decorateClass([
property({ type: Number })
], SynPopup.prototype, "skidding", 2);
__decorateClass([
property({ type: Boolean })
], SynPopup.prototype, "arrow", 2);
__decorateClass([
property({ attribute: "arrow-placement" })
], SynPopup.prototype, "arrowPlacement", 2);
__decorateClass([
property({ attribute: "arrow-padding", type: Number })
], SynPopup.prototype, "arrowPadding", 2);
__decorateClass([
property({ type: Boolean })
], SynPopup.prototype, "flip", 2);
__decorateClass([
property({
attribute: "flip-fallback-placements",
converter: {
fromAttribute: (value) => {
return value.split(" ").map((p) => p.trim()).filter((p) => p !== "");
},
toAttribute: (value) => {
return value.join(" ");
}
}
})
], SynPopup.prototype, "flipFallbackPlacements", 2);
__decorateClass([
property({ attribute: "flip-fallback-strategy" })
], SynPopup.prototype, "flipFallbackStrategy", 2);
__decorateClass([
property({ type: Object })
], SynPopup.prototype, "flipBoundary", 2);
__decorateClass([
property({ attribute: "flip-padding", type: Number })
], SynPopup.prototype, "flipPadding", 2);
__decorateClass([
property({ type: Boolean })
], SynPopup.prototype, "shift", 2);
__decorateClass([
property({ type: Object })
], SynPopup.prototype, "shiftBoundary", 2);
__decorateClass([
property({ attribute: "shift-padding", type: Number })
], SynPopup.prototype, "shiftPadding", 2);
__decorateClass([
property({ attribute: "auto-size" })
], SynPopup.prototype, "autoSize", 2);
__decorateClass([
property()
], SynPopup.prototype, "sync", 2);
__decorateClass([
property({ type: Object })
], SynPopup.prototype, "autoSizeBoundary", 2);
__decorateClass([
property({ attribute: "auto-size-padding", type: Number })
], SynPopup.prototype, "autoSizePadding", 2);
__decorateClass([
property({ attribute: "hover-bridge", type: Boolean })
], SynPopup.prototype, "hoverBridge", 2);
export {
SynPopup
};
//# sourceMappingURL=chunk.6CC5CH2P.js.map