wj-elements
Version:
WebJET Elements is a modern set of user interface tools harnessing the power of web components designed to simplify web application development.
1,183 lines • 60.7 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import WJElement from "./wje-element.js";
import { event } from "./event.js";
const styles = ":host {\n display: block;\n z-index: var(--wje-sliding-container-z-index, 1000);\n position: relative;\n height: 100%;\n right: unset;\n left: unset;\n .close-button {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n }\n}\n\n:host([hide-button]) {\n .close-button {\n display: none;\n }\n}\n\n.sliding-container-backdrop {\n display: none;\n}\n\n.sliding-container-wrapper {\n height: 100%;\n position: relative;\n overflow: hidden;\n}\n\n.native-sliding-container {\n\n position: absolute;\n width: 0;\n height: 100%;\n}\n\n.native-sliding-container-inner {\n height: 100%;\n position: absolute;\n}\n\n.sliding-container-sheet-handle-area {\n display: none;\n}\n\n:host(.bottom-sheet) {\n inset: 0;\n pointer-events: none;\n z-index: var(--wje-sliding-container-bottom-sheet-z-index, var(--wje-sliding-container-z-index, 1000));\n}\n\n:host(.bottom-sheet.open) {\n overscroll-behavior: contain;\n}\n\n:host(.bottom-sheet) .sliding-container-backdrop {\n display: none;\n position: fixed;\n inset: 0;\n z-index: var(--wje-sliding-container-bottom-sheet-backdrop-z-index, 0);\n opacity: 0;\n pointer-events: none;\n touch-action: none;\n overscroll-behavior: none;\n background: var(--wje-sliding-container-backdrop-background, var(--wje-backdrop, rgba(0, 0, 0, .35)));\n}\n\n:host(.bottom-sheet.open) .sliding-container-backdrop {\n pointer-events: auto;\n}\n\n:host(.bottom-sheet) .sliding-container-wrapper {\n position: fixed;\n inset: 0;\n overflow: visible;\n pointer-events: none;\n z-index: var(--wje-sliding-container-bottom-sheet-panel-z-index, 1);\n}\n\n:host(.bottom-sheet) .sliding-container-transparent {\n display: none;\n}\n\n:host(.bottom-sheet) .native-sliding-container {\n position: fixed;\n top: auto;\n height: fit-content;\n overflow: hidden;\n pointer-events: auto;\n display: flex;\n flex-direction: column;\n overscroll-behavior: contain;\n background: var(--wje-sliding-container-background);\n box-shadow: var(--wje-sliding-container-box-shadow, var(--wje-sliding-container-shadow));\n border-radius: var(--wje-sliding-container-sheet-border-radius, var(--wje-sliding-container-border-radius) var(--wje-sliding-container-border-radius) 0 0);\n}\n\n:host(.bottom-sheet) .native-sliding-container-inner {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n height: auto;\n max-height: inherit;\n overflow: auto;\n overscroll-behavior: contain;\n}\n\n:host(.bottom-sheet) .sliding-container-sheet-handle-area {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 var(--wje-sliding-container-sheet-handle-area-height, 24px);\n height: var(--wje-sliding-container-sheet-handle-area-height, 24px);\n cursor: grab;\n pointer-events: auto;\n touch-action: none;\n user-select: none;\n -webkit-user-select: none;\n}\n\n:host(.bottom-sheet) .sliding-container-sheet-handle-area:active {\n cursor: grabbing;\n}\n\n:host(.bottom-sheet) .sliding-container-sheet-handle {\n width: var(--wje-sliding-container-sheet-handle-width, 36px);\n height: var(--wje-sliding-container-sheet-handle-height, 2px);\n background: var(--wje-sliding-container-sheet-handle-background, var(--wje-border-color));\n border-radius: var(--wje-sliding-container-sheet-handle-radius, 999px);\n}\n";
class SlidingContainer extends WJElement {
/**
* Creates an instance of SlidingContainer.
* @class
*/
constructor() {
super();
__publicField(this, "className", "SlidingContainer");
/**
* Handles backdrop click dismissal.
* @param {MouseEvent} e The click event.
*/
__publicField(this, "handleBackdropClick", (e) => {
if (this.isEventInsideSheet(e) || Date.now() < this._sheetIgnoreDismissUntil || Date.now() < this._sheetBackdropHandledUntil) {
e.preventDefault();
e.stopPropagation();
return;
}
if (this.isBackdropElement(e.target)) {
this.close(e);
}
});
/**
* Stops a handle interaction from dismissing the bottom sheet.
* @param {Event} e The interaction emitted by the resize handle.
*/
__publicField(this, "stopSheetHandleEvent", (e) => {
var _a;
if (!this.isBottomSheet()) return;
this._sheetIgnoreDismissUntil = Date.now() + 500;
e.preventDefault();
e.stopPropagation();
(_a = e.stopImmediatePropagation) == null ? void 0 : _a.call(e);
});
/**
* Prevents scroll events from reaching the page behind the bottom sheet.
* @param {Event} e The scroll event.
*/
__publicField(this, "preventBottomSheetScroll", (e) => {
if (!this.isBottomSheet() || !this.classList.contains("open")) return;
e.preventDefault();
e.stopPropagation();
});
/**
* Starts mobile bottom sheet resizing.
* @param {PointerEvent} e The pointer event.
*/
__publicField(this, "handleSheetDragStart", (e) => {
var _a, _b;
if (!this.sheetResizable || !this.isBottomSheet()) return;
e.preventDefault();
e.stopPropagation();
(_a = e.stopImmediatePropagation) == null ? void 0 : _a.call(e);
this._sheetIgnoreDismissUntil = Date.now() + 500;
if (this._sheetDrag) return;
this.startSheetDrag(e.pointerId, e.clientY);
try {
(_b = this.sheetHandleArea) == null ? void 0 : _b.setPointerCapture(e.pointerId);
} catch {
}
window.addEventListener("pointermove", this.handleSheetDragMove);
window.addEventListener("pointerup", this.handleSheetDragEnd);
window.addEventListener("pointercancel", this.handleSheetDragEnd);
});
/**
* Starts mobile bottom sheet resizing from a touch event.
* @param {TouchEvent} e The touch event.
*/
__publicField(this, "handleSheetTouchStart", (e) => {
var _a, _b;
if (!this.sheetResizable || !this.isBottomSheet() || this._sheetDrag) return;
const touch = (_a = e.touches) == null ? void 0 : _a[0];
if (!touch) return;
e.preventDefault();
e.stopPropagation();
(_b = e.stopImmediatePropagation) == null ? void 0 : _b.call(e);
this._sheetIgnoreDismissUntil = Date.now() + 500;
this.startSheetDrag("touch", touch.clientY);
window.addEventListener("touchmove", this.handleSheetTouchMove, { passive: false });
window.addEventListener("touchend", this.handleSheetTouchEnd, { passive: false });
window.addEventListener("touchcancel", this.handleSheetTouchEnd, { passive: false });
});
/**
* Starts tracking a possible backdrop dismiss tap.
* @param {PointerEvent} e The pointer event.
*/
__publicField(this, "handleBackdropPointerStart", (e) => {
if (!this.isBottomSheet() || !this.isBackdropElement(e.target) || this.isEventInsideSheet(e) || Date.now() < this._sheetIgnoreDismissUntil) {
this._sheetBackdropGesture = null;
return;
}
this._sheetBackdropGesture = {
pointerId: e.pointerId,
startX: e.clientX,
startY: e.clientY,
hasMoved: false
};
});
/**
* Tracks movement during a possible backdrop dismiss tap.
* @param {PointerEvent} e The pointer event.
*/
__publicField(this, "handleBackdropPointerMove", (e) => {
if (!this._sheetBackdropGesture || e.pointerId !== this._sheetBackdropGesture.pointerId) return;
const distanceX = Math.abs(e.clientX - this._sheetBackdropGesture.startX);
const distanceY = Math.abs(e.clientY - this._sheetBackdropGesture.startY);
if (distanceX > 8 || distanceY > 8) {
this._sheetBackdropGesture.hasMoved = true;
}
});
/**
* Finishes a possible backdrop dismiss tap.
* @param {PointerEvent} e The pointer event.
*/
__publicField(this, "handleBackdropPointerEnd", (e) => {
if (!this._sheetBackdropGesture || e.pointerId !== this._sheetBackdropGesture.pointerId) return;
const shouldClose = !this._sheetBackdropGesture.hasMoved && this.isBackdropElement(e.target) && !this.isEventInsideSheet(e) && Date.now() >= this._sheetIgnoreDismissUntil;
this._sheetBackdropGesture = null;
this._sheetBackdropHandledUntil = Date.now() + 350;
if (shouldClose) {
e.preventDefault();
e.stopPropagation();
this.close(e);
}
});
/**
* Cancels a possible backdrop dismiss tap.
*/
__publicField(this, "handleBackdropPointerCancel", () => {
this._sheetBackdropGesture = null;
this._sheetBackdropHandledUntil = Date.now() + 350;
});
/**
* Resizes the mobile bottom sheet during dragging.
* @param {PointerEvent} e The pointer event.
*/
__publicField(this, "handleSheetDragMove", (e) => {
var _a;
if (!this._sheetDrag || e.pointerId !== this._sheetDrag.pointerId) return;
e.preventDefault();
e.stopPropagation();
(_a = e.stopImmediatePropagation) == null ? void 0 : _a.call(e);
this.updateSheetDrag(e.clientY);
});
/**
* Resizes the mobile bottom sheet during touch dragging.
* @param {TouchEvent} e The touch event.
*/
__publicField(this, "handleSheetTouchMove", (e) => {
var _a, _b;
if (!this._sheetDrag || this._sheetDrag.pointerId !== "touch") return;
const touch = (_a = e.touches) == null ? void 0 : _a[0];
if (!touch) return;
e.preventDefault();
e.stopPropagation();
(_b = e.stopImmediatePropagation) == null ? void 0 : _b.call(e);
this.updateSheetDrag(touch.clientY);
});
/**
* Ends mobile bottom sheet resizing.
* @param {PointerEvent} e The pointer event.
*/
__publicField(this, "handleSheetDragEnd", (e) => {
var _a, _b, _c;
const hasMoved = (_a = this._sheetDrag) == null ? void 0 : _a.hasMoved;
if (this._sheetDrag && (e == null ? void 0 : e.pointerId) === this._sheetDrag.pointerId) {
e.preventDefault();
e.stopPropagation();
(_b = e.stopImmediatePropagation) == null ? void 0 : _b.call(e);
try {
(_c = this.sheetHandleArea) == null ? void 0 : _c.releasePointerCapture(e.pointerId);
} catch {
}
}
if (hasMoved) {
this._sheetIgnoreDismissUntil = Date.now() + 350;
} else {
this._sheetIgnoreDismissUntil = Date.now() + 500;
}
this.endSheetDrag();
});
/**
* Ends mobile bottom sheet resizing from a touch event.
* @param {TouchEvent} e The touch event.
*/
__publicField(this, "handleSheetTouchEnd", (e) => {
var _a;
if (!this._sheetDrag || this._sheetDrag.pointerId !== "touch") return;
const hasMoved = this._sheetDrag.hasMoved;
if (e.type !== "touchcancel") {
e.preventDefault();
}
e.stopPropagation();
if (e.type !== "touchcancel") {
(_a = e.stopImmediatePropagation) == null ? void 0 : _a.call(e);
}
this._sheetIgnoreDismissUntil = Date.now() + (hasMoved ? 350 : 700);
this.endSheetDrag();
});
/**
* Triggers the event based on the target element.
* If the target element is different from the last caller, it refreshes the children by calling the `open` method.
* If the target element is the same as the last caller, it toggles the state by calling the `toggle` method.
* @param {Event} e The event object.
*/
__publicField(this, "triggerEvent", async (e) => {
if (this._lastCaller && this._lastCaller !== e.composedPath()[0]) {
await this.open(e);
} else {
await this.toggle(e);
}
this._lastCaller = e.composedPath()[0];
});
this._isOpen = false;
this._lastCaller = null;
this._sheetDrag = null;
this._sheetIgnoreDismissUntil = 0;
this._sheetBackdropGesture = null;
this._sheetBackdropHandledUntil = 0;
this._bottomSheetScopeFrame = null;
this._resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
if (entry.contentBoxSize) {
if (this.drawingStatus < 3) return;
if (this.isBottomSheet()) {
this.checkForVariant(this.variant);
} else if (this.screenBreakPoint && window.innerWidth <= this.screenBreakPoint) {
if (this.variant !== "over") {
this.variant = "over";
} else {
this.checkForVariant(this.variant);
}
} else {
if (this.variant !== "in-place") {
this.variant = "in-place";
} else {
this.checkForVariant(this.variant);
}
}
}
}
});
this._resizeObserver.observe(document.documentElement);
}
/**
* Sets the maximum width of an element by updating the 'max-width' attribute.
* @param {string} value The maximum width value to be set (e.g., '100px', '50%', etc.).
*/
set maxWidth(value) {
this.setAttribute("max-width", value);
}
/**
* Gets the maximum width value of the element.
* Retrieves the value of the 'max-width' attribute. If the attribute is not set, it defaults to 'auto'.
* @returns {string} The maximum width value of the element or 'auto' if the attribute is not defined.
*/
get maxWidth() {
return this.getAttribute("max-width") ?? "auto";
}
/**
* Sets the maximum height for the element.
* @param {string} value The maximum height value to be applied to the element. This can include units such as "px", "em", "%", etc.
*/
set maxHeight(value) {
this.setAttribute("max-height", value);
}
/**
* Retrieves the maximum height value of the element, or returns 'auto' if not set.
* @returns {string} The maximum height value or 'auto' if the attribute is not specified.
*/
get maxHeight() {
return this.getAttribute("max-height") ?? "auto";
}
/**
* Sets the 'trigger' attribute for the element.
* @param {string} value The value to set for the 'trigger' attribute.
*/
set trigger(value) {
this.setAttribute("trigger", value);
}
/**
* Retrieves the value of the 'trigger' attribute. If the attribute is not set, it defaults to 'sliding-container'.
* @returns {string} The value of the 'trigger' attribute or the default value 'sliding-container' if not defined.
*/
get trigger() {
return this.getAttribute("trigger") ?? "sliding-container";
}
/**
* Sets the direction attribute for the element.
* @param {string} value The direction value to be assigned. Possible values are typically 'ltr' (left-to-right), 'rtl' (right-to-left), or 'auto'.
*/
set direction(value) {
this.setAttribute("direction", value);
}
/**
* Retrieves the direction attribute of the instance.
* If the direction attribute is not set, it defaults to 'right'.
* @returns {string} The value of the direction attribute or 'right' if not set.
*/
get direction() {
return this.getAttribute("direction") ?? "right";
}
/**
* Sets the value of the `remove-child-after-close` attribute.
* This attribute determines if a child element should be removed after a close operation.
* @param {boolean|string} value The value to set for the `remove-child-after-close` attribute. The value can be a boolean or a string representation of a boolean.
*/
set removeChildAfterClose(value) {
this.setAttribute("remove-child-after-close", value);
}
/**
* Gets the value indicating whether the child element should be removed after closing.
*
* This property checks the presence of the 'remove-child-after-close' attribute on the element.
* Returns `false` if the attribute does not exist.
* @returns {boolean} True if the 'remove-child-after-close' attribute is present, otherwise false.
*/
get removeChildAfterClose() {
return this.hasAttribute("remove-child-after-close") ?? false;
}
/**
* Sets the 'variant' attribute to the specified value.
* @param {string} value The value to set for the 'variant' attribute.
*/
set variant(value) {
this.setAttribute("variant", value);
}
/**
* Retrieves the value of the "variant" attribute. If the attribute is not set,
* it returns the default value 'in-place'.
* @returns {string} The variant value or the default value 'in-place'.
*/
get variant() {
return this.getAttribute("variant") ?? "in-place";
}
/**
* Retrieves the value of the 'screen-break-point' attribute.
* @returns {string} The value of the 'screen-break-point' attribute.
*/
get screenBreakPoint() {
return this.getAttribute("screen-break-point");
}
/**
* Sets the screen break point value to determine responsive behavior.
* @param {string} value The value to set as the screen break point.
*/
set screenBreakPoint(value) {
this.setAttribute("screen-break-point", value);
}
/**
* Sets mobile presentation mode.
* @param {string} value The mobile presentation mode.
*/
set mobilePresentation(value) {
this.setAttribute("mobile-presentation", value);
}
/**
* Gets mobile presentation mode.
* @returns {string|null} The mobile presentation mode.
*/
get mobilePresentation() {
return this.getAttribute("mobile-presentation");
}
/**
* Sets the breakpoint for mobile presentation.
* @param {string} value The mobile breakpoint.
*/
set mobileBreakPoint(value) {
this.setAttribute("mobile-break-point", value);
}
/**
* Gets the breakpoint for mobile presentation.
* @returns {string} The mobile breakpoint.
*/
get mobileBreakPoint() {
return this.getAttribute("mobile-break-point") || "768";
}
/**
* Sets the duration of the animation by updating the `animation-duration` attribute.
* @param {string} value The duration value for the animation, specified in a format
* such as seconds (e.g., "2s") or milliseconds (e.g., "200ms").
*/
set animationDuration(value) {
this.setAttribute("animation-duration", value);
}
/**
* Gets the animation duration for an element.
* It retrieves the value of the 'animation-duration' attribute if present; otherwise, it defaults to '500'.
* @returns {string} The value of the animation duration, either from the attribute or the default '500'.
*/
get animationDuration() {
return this.getAttribute("animation-duration") ?? "500";
}
/**
* Sets the easing function for the animation.
* @param {string} value The easing function to use for the animation. This can be any valid CSS timing function such as "ease", "linear", "ease-in", "ease-out", etc.
*/
set animationEasing(value) {
this.setAttribute("animation-easing", value);
}
/**
* Retrieves the easing function for the animation.
* @returns {string} The value of the 'animation-easing' attribute if set, otherwise defaults to 'linear'.
*/
get animationEasing() {
return this.getAttribute("animation-easing") ?? "linear";
}
/**
* Determines if the element has an 'has-opacity' attribute.
* @returns {boolean} True if the element has the 'has-opacity' attribute, otherwise false.
*/
get hasOpacity() {
return this.hasAttribute("has-opacity") ?? false;
}
/**
* Determines if the bottom sheet should close when the backdrop is clicked.
* @returns {boolean} True if backdrop dismiss is enabled.
*/
get backdropDismiss() {
return this.hasAttribute("backdrop-dismiss") ?? false;
}
/**
* Enables resizing for the mobile bottom sheet.
* @returns {boolean} True if the sheet can be resized.
*/
get sheetResizable() {
return this.hasAttribute("sheet-resizable") ?? false;
}
/**
* Sets resizing for the mobile bottom sheet.
* @param {boolean|string} value The value to set.
*/
set sheetResizable(value) {
this.setAttribute("sheet-resizable", value);
}
/**
* Gets the mobile bottom sheet panel scope. The backdrop always covers the viewport.
* @returns {string} Either 'viewport', 'container', or 'parent'.
*/
get sheetScope() {
return this.getAttribute("sheet-scope") || "viewport";
}
/**
* Sets the mobile bottom sheet panel scope.
* @param {string} value The new boundary mode for the mobile sheet.
*/
set sheetScope(value) {
this.setAttribute("sheet-scope", value);
}
/**
* Gets the selector used for container-scoped mobile bottom sheet panels.
* @returns {string|null} A CSS selector used to find the composed boundary ancestor.
*/
get sheetBoundary() {
return this.getAttribute("sheet-boundary");
}
/**
* Sets the selector used for container-scoped mobile bottom sheet panels.
* @param {string} value Selector for the boundary element.
*/
set sheetBoundary(value) {
this.setAttribute("sheet-boundary", value);
}
/**
* Gets the mobile bottom sheet opening height.
* @returns {string|null} The opening height.
*/
get sheetHeight() {
return this.getAttribute("sheet-height");
}
/**
* Sets the mobile bottom sheet opening height.
* @param {string} value The opening height.
*/
set sheetHeight(value) {
this.setAttribute("sheet-height", value);
}
/**
* Gets the minimum mobile bottom sheet height while resizing.
* @returns {string|null} The minimum height.
*/
get sheetMinHeight() {
return this.getAttribute("sheet-min-height");
}
/**
* Sets the minimum mobile bottom sheet height while resizing.
* @param {string} value The minimum height.
*/
set sheetMinHeight(value) {
this.setAttribute("sheet-min-height", value);
}
/**
* Gets the maximum mobile bottom sheet height while resizing.
* @returns {string|null} The maximum height.
*/
get sheetMaxHeight() {
return this.getAttribute("sheet-max-height");
}
/**
* Sets the maximum mobile bottom sheet height while resizing.
* @param {string} value The maximum height.
*/
set sheetMaxHeight(value) {
this.setAttribute("sheet-max-height", value);
}
/**
* Sets the value of the 'add-to-height' attribute.
* This attribute is used to modify or adjust the height dynamically.
* @param {string} value The value to be assigned to the 'add-to-height' attribute.
*/
set addToHeight(value) {
this.setAttribute("add-to-height", value);
}
/**
* Retrieves the value of the 'add-to-height' attribute from the element.
* If the attribute is not set, it defaults to '0'.
* @returns {string} The value of the 'add-to-height' attribute or '0' if the attribute is not present.
*/
get addToHeight() {
return this.getAttribute("add-to-height") ?? "0";
}
/**
* Determines whether the current state is open.
* @returns {boolean} True if the state is open, otherwise false.
*/
get isOpen() {
return this._isOpen;
}
/**
* Returns the observed attributes for the component.
* @returns {string[]}
*/
static get observedAttributes() {
return ["max-width", "max-height", "trigger", "direction", "variant", "screen-break-point", "remove-child-after-close", "animation-duration", "animation-easing", "has-opacity", "mobile-presentation", "mobile-break-point", "backdrop-dismiss", "sheet-resizable", "sheet-scope", "sheet-boundary", "sheet-height", "sheet-min-height", "sheet-max-height"];
}
/**
* Returns the CSS styles for the component.
* @static
* @returns {CSSStyleSheet}
*/
static get cssStyleSheet() {
return styles;
}
/**
* Sets up the attributes for the component.
*/
setupAttributes() {
this.isShadowRoot = "open";
this.syncAria();
}
/**
* Executes before drawing the element.
*/
beforeDraw() {
var _a, _b;
(_a = this.animation) == null ? void 0 : _a.cancel();
(_b = this.nativeAnimation) == null ? void 0 : _b.cancel();
this.endSheetDrag();
this.removePortalBackdrop();
document.removeEventListener(this.trigger, this.triggerEvent);
}
/**
* Cleans up global listeners and portaled layers.
*/
afterDisconnect() {
var _a, _b, _c;
(_a = this.animation) == null ? void 0 : _a.cancel();
(_b = this.nativeAnimation) == null ? void 0 : _b.cancel();
this.endSheetDrag();
this.removePortalBackdrop();
(_c = this._resizeObserver) == null ? void 0 : _c.disconnect();
document.removeEventListener(this.trigger, this.triggerEvent);
}
/**
* Draws the component.
* @param {object} context The context for drawing.
* @param {object} store The store for drawing.
* @param {object} params The parameters for drawing.
* @returns {DocumentFragment}
*/
draw(context, store, params) {
let fragment = document.createDocumentFragment();
const isBottomSheet = this.isBottomSheet();
let backdrop = document.createElement("div");
backdrop.setAttribute("part", "backdrop");
backdrop.classList.add("sliding-container-backdrop");
if (this._isOpen && isBottomSheet) {
backdrop.style.opacity = "var(--wje-sliding-container-backdrop-opacity, 1)";
}
this.addBackdropListeners(backdrop);
let wrapperDiv = document.createElement("div");
wrapperDiv.classList.add("sliding-container-wrapper");
let transparentDiv = document.createElement("div");
transparentDiv.classList.add("sliding-container-transparent");
if (this._isOpen && !isBottomSheet) {
transparentDiv.style.width = this.maxWidth;
}
let native = document.createElement("div");
native.setAttribute("part", "sliding-container");
native.classList.add("native-sliding-container");
native.style.width = isBottomSheet ? "100%" : 0;
if (this.hasOpacity) {
native.style.opacity = 0;
}
if (this._isOpen) {
native.style.width = isBottomSheet ? "100%" : this.maxWidth;
if (isBottomSheet) {
native.style.transform = "translateY(0)";
}
if (this.hasOpacity) {
native.style.opacity = 1;
}
} else if (isBottomSheet) {
native.style.transform = "translateY(100%)";
}
if (isBottomSheet) {
native.style.left = 0;
native.style.right = 0;
native.style.bottom = 0;
native.style.maxHeight = this.maxHeight;
} else if (this.direction === "right") {
native.style.right = 0;
} else {
native.style.left = 0;
}
const nativeInner = document.createElement("div");
nativeInner.classList.add("native-sliding-container-inner");
nativeInner.style.width = isBottomSheet ? "100%" : this.maxWidth;
if (isBottomSheet) {
nativeInner.style.maxHeight = this.maxHeight;
}
let slot = document.createElement("slot");
this.sheetHandleArea = null;
nativeInner.append(slot);
nativeInner.append(this.htmlCloseButton());
if (this.sheetResizable) {
native.append(this.htmlSheetHandle());
}
native.append(nativeInner);
wrapperDiv.append(transparentDiv);
wrapperDiv.append(native);
fragment.append(backdrop);
fragment.append(wrapperDiv);
this.backdropElement = backdrop;
this.transparentDiv = transparentDiv;
this.wrapperDiv = wrapperDiv;
this.nativeElement = native;
this.nativeInner = nativeInner;
return fragment;
}
/**
* Performs actions after the element is drawn on the screen.
* Attaches an event listener to the document based on the specified trigger.
* Sets the variant to "over" if the document width is smaller than the screen break point.
* Calls the checkForVariant method with the current variant.
*/
afterDraw() {
this.syncAria();
document.addEventListener(this.trigger, this.triggerEvent);
if (!this.isBottomSheet() && this.screenBreakPoint && window.innerWidth <= this.screenBreakPoint) {
this.variant = "over";
}
this.checkForVariant(this.variant);
}
/**
* Returns whether the mobile bottom sheet presentation is active.
* @returns {boolean}
*/
isBottomSheet() {
return this.mobilePresentation === "bottom-sheet" && window.innerWidth <= +this.mobileBreakPoint;
}
/**
* Adds interaction listeners to a backdrop element.
* @param {HTMLElement} backdrop The backdrop element.
*/
addBackdropListeners(backdrop) {
backdrop.addEventListener("wheel", this.preventBottomSheetScroll, { passive: false });
backdrop.addEventListener("touchmove", this.preventBottomSheetScroll, { passive: false });
if (!this.backdropDismiss) return;
backdrop.addEventListener("pointerdown", this.handleBackdropPointerStart);
backdrop.addEventListener("pointermove", this.handleBackdropPointerMove);
backdrop.addEventListener("pointerup", this.handleBackdropPointerEnd);
backdrop.addEventListener("pointercancel", this.handleBackdropPointerCancel);
backdrop.addEventListener("click", this.handleBackdropClick);
}
/**
* Removes interaction listeners from a backdrop element.
* @param {HTMLElement} backdrop The backdrop element.
*/
removeBackdropListeners(backdrop) {
backdrop.removeEventListener("wheel", this.preventBottomSheetScroll);
backdrop.removeEventListener("touchmove", this.preventBottomSheetScroll);
backdrop.removeEventListener("pointerdown", this.handleBackdropPointerStart);
backdrop.removeEventListener("pointermove", this.handleBackdropPointerMove);
backdrop.removeEventListener("pointerup", this.handleBackdropPointerEnd);
backdrop.removeEventListener("pointercancel", this.handleBackdropPointerCancel);
backdrop.removeEventListener("click", this.handleBackdropClick);
}
/**
* Checks whether an element is one of this component's backdrop layers.
* @param {EventTarget|null} element The event target.
* @returns {boolean}
*/
isBackdropElement(element) {
return element === this.backdropElement || element === this._portalBackdropElement;
}
/**
* Gets the backdrop element that is active for the current presentation.
* @param {object} options Options for resolving the backdrop.
* @param {boolean} options.createPortal Creates the body-level mobile backdrop when needed.
* @returns {HTMLElement|null}
*/
getBackdropElement({ createPortal = false } = {}) {
if (!this.isBottomSheet()) return this.backdropElement;
if (createPortal) return this.ensurePortalBackdrop();
return this._portalBackdropElement || this.backdropElement;
}
/**
* Creates the body-level backdrop used by mobile bottom sheets.
* @returns {HTMLElement} Element appended to document.body for page-level dimming.
*/
ensurePortalBackdrop() {
if (!this._portalBackdropElement) {
this._portalBackdropElement = document.createElement("div");
this._portalBackdropElement.setAttribute("data-wje-sliding-container-backdrop", "");
this._portalBackdropElement.classList.add("sliding-container-backdrop", "sliding-container-backdrop-portal");
this.addBackdropListeners(this._portalBackdropElement);
document.body.append(this._portalBackdropElement);
}
this.syncPortalBackdropStyles();
return this._portalBackdropElement;
}
/**
* Removes the body-level mobile backdrop.
*/
removePortalBackdrop() {
if (!this._portalBackdropElement) return;
this.removeBackdropListeners(this._portalBackdropElement);
this._portalBackdropElement.remove();
this._portalBackdropElement = null;
}
/**
* Gets the full-page z-index for the body-level mobile backdrop.
* @returns {string}
*/
getPortalBackdropZIndex() {
const hostStyle = getComputedStyle(this);
const configuredZIndex = hostStyle.getPropertyValue("--wje-sliding-container-portal-backdrop-z-index").trim();
if (configuredZIndex) return configuredZIndex;
const hostZIndex = Number.parseInt(hostStyle.zIndex, 10);
if (Number.isFinite(hostZIndex)) return String(hostZIndex - 1);
return "999";
}
/**
* Gets the target opacity for the active mobile backdrop.
* @returns {string}
*/
getBackdropOpenOpacity() {
return getComputedStyle(this).getPropertyValue("--wje-sliding-container-backdrop-opacity").trim() || "1";
}
/**
* Syncs visual styles from the component to the body-level backdrop.
*/
syncPortalBackdropStyles() {
if (!this._portalBackdropElement) return;
const backdropStyle = this.backdropElement ? getComputedStyle(this.backdropElement) : null;
this._portalBackdropElement.style.position = "fixed";
this._portalBackdropElement.style.inset = "0";
this._portalBackdropElement.style.display = "block";
this._portalBackdropElement.style.opacity = this.classList.contains("open") ? this.getBackdropOpenOpacity() : "0";
this._portalBackdropElement.style.pointerEvents = this.classList.contains("open") ? "auto" : "none";
this._portalBackdropElement.style.touchAction = "none";
this._portalBackdropElement.style.overscrollBehavior = "none";
this._portalBackdropElement.style.background = (backdropStyle == null ? void 0 : backdropStyle.background) || "rgba(0, 0, 0, .35)";
this._portalBackdropElement.style.zIndex = this.getPortalBackdropZIndex();
}
/**
* Sync ARIA attributes on host.
*/
syncAria() {
if (!this.hasAttribute("role")) {
this.setAriaState({ role: "region" });
}
this.setAriaState({ hidden: !this.isOpen });
const ariaLabel = this.getAttribute("aria-label");
const label = this.getAttribute("label");
if (!ariaLabel && label) {
this.setAriaState({ label });
}
}
/**
* Creates and returns a styled close button element with an icon,
* including an event listener to trigger the close method.
* @returns {HTMLElement} The close button element configured with styles, an icon, and event listener.
*/
htmlCloseButton() {
let closeButton = document.createElement("wje-button");
closeButton.setAttribute("part", "close-button");
closeButton.classList.add("close-button");
let icon = document.createElement("wje-icon");
icon.setAttribute("slot", "icon-only");
icon.setAttribute("name", "x");
closeButton.append(icon);
event.addListener(closeButton, "wje-button:click", null, (e) => {
this.close();
});
return closeButton;
}
/**
* Creates the mobile bottom sheet resize handle.
* @returns {HTMLElement} The resize handle element.
*/
htmlSheetHandle() {
const handleArea = document.createElement("div");
handleArea.setAttribute("part", "sheet-handle-area");
handleArea.setAttribute("role", "separator");
handleArea.setAttribute("aria-orientation", "horizontal");
handleArea.classList.add("sliding-container-sheet-handle-area");
const handle = document.createElement("div");
handle.setAttribute("part", "sheet-handle");
handle.classList.add("sliding-container-sheet-handle");
handleArea.append(handle);
handleArea.addEventListener("pointerdown", this.handleSheetDragStart, { capture: true });
handleArea.addEventListener("touchstart", this.handleSheetTouchStart, { capture: true, passive: false });
handleArea.addEventListener("click", this.stopSheetHandleEvent, { capture: true });
handleArea.addEventListener("mousedown", this.stopSheetHandleEvent, { capture: true });
handleArea.addEventListener("mouseup", this.stopSheetHandleEvent, { capture: true });
this.sheetHandleArea = handleArea;
return handleArea;
}
/**
* Retrieves the parent element of the current element.
* If the parent element is not found, it attempts to find the root host element.
* @returns {Element|null} The parent element or the root host element if no parent exists. Returns null if neither is found.
*/
getParentElement() {
let parentElement = this.parentElement;
if (!parentElement) {
parentElement = this.getRootNode().host;
}
return parentElement;
}
/**
* Finds the closest element across shadow DOM boundaries.
* @param {string} selector The selector to match.
* @returns {Element|null} The matched composed ancestor.
*/
getClosestComposedElement(selector) {
var _a;
let currentElement = this;
while (currentElement) {
let matches = false;
try {
matches = currentElement instanceof Element && currentElement.matches(selector);
} catch {
return null;
}
if (matches) {
return currentElement;
}
if (currentElement.parentElement) {
currentElement = currentElement.parentElement;
continue;
}
const root = (_a = currentElement.getRootNode) == null ? void 0 : _a.call(currentElement);
currentElement = root instanceof ShadowRoot ? root.host : null;
}
return null;
}
/**
* Returns the viewport frame.
* @returns {{top: number, left: number, right: number, bottom: number, width: number, height: number}}
*/
getViewportFrame() {
return {
top: 0,
left: 0,
right: window.innerWidth,
bottom: window.innerHeight,
width: window.innerWidth,
height: window.innerHeight
};
}
/**
* Returns the element that bounds the mobile bottom sheet panel.
* @returns {Element|null} The scope element, or null for viewport scope.
*/
getBottomSheetScopeElement() {
if (this.sheetScope === "viewport") return null;
if (this.sheetScope === "parent") return this.getParentElement();
if (this.sheetBoundary) return this.getClosestComposedElement(this.sheetBoundary);
return this.getParentElement();
}
/**
* Returns the frame used by the mobile bottom sheet panel.
* @returns {{top: number, left: number, right: number, bottom: number, width: number, height: number}}
*/
getBottomSheetScopeFrame() {
const scopeElement = this.getBottomSheetScopeElement();
if (!scopeElement) return this.getViewportFrame();
const rect = scopeElement.getBoundingClientRect();
const top = Math.max(rect.top, 0);
const left = Math.max(rect.left, 0);
const right = Math.min(rect.right, window.innerWidth);
const bottom = Math.min(rect.bottom, window.innerHeight);
const width = right - left;
const height = bottom - top;
if (width <= 0 || height <= 0) {
return this.getViewportFrame();
}
return {
top,
left,
right,
bottom,
width,
height
};
}
/**
* Applies a fixed frame to a bottom sheet layer.
* @param {HTMLElement|SlidingContainer} element The element to update.
* @param {object} frame The frame to apply.
*/
setBottomSheetLayerFrame(element, frame) {
element.style.top = frame.top + "px";
element.style.left = frame.left + "px";
element.style.right = "auto";
element.style.bottom = "auto";
element.style.width = frame.width + "px";
element.style.height = frame.height + "px";
}
/**
* Removes fixed frame styles from a bottom sheet layer.
* @param {HTMLElement|SlidingContainer} element The element to reset.
*/
resetBottomSheetLayerFrame(element) {
element == null ? void 0 : element.style.removeProperty("top");
element == null ? void 0 : element.style.removeProperty("left");
element == null ? void 0 : element.style.removeProperty("right");
element == null ? void 0 : element.style.removeProperty("bottom");
element == null ? void 0 : element.style.removeProperty("width");
element == null ? void 0 : element.style.removeProperty("height");
}
/**
* Adjusts the position and dimensions of the current element based on the specified variant.
*
* The method handles modifications to the element's positioning style, aligns it relative to its parent,
* and manages alignment to its siblings based on the specified direction.
* @param {string} variant The variant to determine how the element should be updated. For example, when set to 'over', specific adjustments to the position and size are performed.
* @returns {void} No value is returned, the method modifies the element's style properties directly.
*/
checkForVariant(variant) {
if (this.isBottomSheet()) {
this.setBottomSheetVariant();
return;
}
this.resetBottomSheetVariant();
if (variant === "over") {
this.style.position = "fixed";
let computentStyleOfParent = window.getComputedStyle(this.getParentElement());
let parentElementBoundingbox = this.getParentElement().getBoundingClientRect();
let heightOfParrentElement = parseFloat(computentStyleOfParent.height);
let topOfParrentElement = parseFloat(computentStyleOfParent.top);
this.style.height = heightOfParrentElement + +this.addToHeight + "px";
this.wrapperDiv.style.height = heightOfParrentElement + +this.addToHeight + "px";
this.style.top = topOfParrentElement + "px";
const leftSibling = this.previousElementSibling;
const rightSibling = this.nextElementSibling;
const leftSiblingBoundingbox = leftSibling == null ? void 0 : leftSibling.getBoundingClientRect();
const rightSiblingBoundingbox = rightSibling == null ? void 0 : rightSibling.getBoundingClientRect();
if (this.direction === "right") {
if (leftSiblingBoundingbox) {
this.style.left = leftSiblingBoundingbox.left + leftSiblingBoundingbox.width + "px";
} else {
this.style.left = parentElementBoundingbox.left + "px";
}
} else {
if (rightSiblingBoundingbox) {
this.style.right = window.innerWidth - rightSiblingBoundingbox.left + "px";
} else {
this.style.right = window.innerWidth - (parentElementBoundingbox.left + parentElementBoundingbox.width) + "px";
}
}
}
}
/**
* Applies the mobile bottom sheet layout.
*/
setBottomSheetVariant() {
const scopeFrame = this.getBottomSheetScopeFrame();
const backdropFrame = this.getViewportFrame();
const backdrop = this.getBackdropElement({
createPortal: this.classList.contains("open") || this._isOpen
});
this._bottomSheetScopeFrame = scopeFrame;
this.classList.add("bottom-sheet");
this.style.position = "fixed";
this.setBottomSheetLayerFrame(this, scopeFrame);
this.setBottomSheetLayerFrame(backdrop, backdropFrame);
this.setBottomSheetLayerFrame(this.wrapperDiv, scopeFrame);
this.syncPortalBackdropStyles();
this.wrapperDiv.style.height = "100%";
this.transparentDiv.style.width = 0;
this.nativeElement.style.left = scopeFrame.left + "px";
this.nativeElement.style.right = "auto";
this.nativeElement.style.bottom = window.innerHeight - scopeFrame.bottom + "px";
this.nativeElement.style.top = "auto";
this.nativeElement.style.width = scopeFrame.width + "px";
if (this._sheetDrag) {
this.nativeElement.style.maxHeight = this.getSheetMaxHeightInPixels() + "px";
} else {
this.setSheetHeight();
}
this.setBottomSheetVisualState(this.classList.contains("open"));
this.nativeInner.style.width = "100%";
this.nativeInner.style.maxHeight = this.nativeElement.style.maxHeight;
}
/**
* Removes layout styles managed by the mobile bottom sheet mode.
*/
resetBottomSheetVariant() {
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
if (!this.classList.contains("bottom-sheet")) return;
this.classList.remove("bottom-sheet");
this.style.removeProperty("position");
this.resetBottomSheetLayerFrame(this);
this.resetBottomSheetLayerFrame(this.backdropElement);
this.removePortalBackdrop();
this.resetBottomSheetLayerFrame(this.wrapperDiv);
if (this.transparentDiv) {
this.transparentDiv.style.width = this._isOpen ? this.maxWidth : 0;
}
(_a = this.nativeElement) == null ? void 0 : _a.style.removeProperty("left");
(_b = this.nativeElement) == null ? void 0 : _b.style.removeProperty("right");
(_c = this.nativeElement) == null ? void 0 : _c.style.removeProperty("bottom");
(_d = this.nativeElement) == null ? void 0 : _d.style.removeProperty("top");
(_e = this.nativeElement) == null ? void 0 : _e.style.removeProperty("height");
(_f = this.nativeElement) == null ? void 0 : _f.style.removeProperty("max-height");
(_g = this.nativeElement) == null ? void 0 : _g.style.removeProperty("transform");
if (this.nativeElement) {
if (this.direction === "right") {
this.nativeElement.style.right = 0;
} else {
this.nativeElement.style.left = 0;
}
this.nativeElement.style.width = this._isOpen ? this.maxWidth : 0;
}
(_h = this.nativeInner) == null ? void 0 : _h.style.removeProperty("max-height");
if (this.nativeInner) {
this.nativeInner.style.width = this.maxWidth;
}
(_i = this.backdropElement) == null ? void 0 : _i.style.removeProperty("opacity");
this._bottomSheetScopeFrame = null;
this.endSheetDrag();
}
/**
* Applies the current visual open or closed state to the mobile bottom sheet.
* @param {boolean} isOpen True when the sheet should be visible.
*/
setBottomSheetVisualState(isOpen) {
this.nativeElement.style.transform = isOpen ? "translateY(0)" : "translateY(100%)";
const backdrop = this.getBackdropElement({ createPortal: isOpen || Boolean(this._portalBackdropElement) });
if (this.hasOpacity) {
this.nativeElement.style.opacity = isOpen ? 1 : 0;
}
if (backdrop) {
backdrop.style.opacity = isOpen ? this.getBackdropOpenOpacity() : "0";
backdrop.style.pointerEvents = isOpen ? "auto" : "none";
}
if (!isOpen) {
this.removePortalBackdrop();
}
}
/**
* Gets the CSS height limit used for the mobile bottom sheet.
* @returns {string} The CSS max height.
*/
getSheetMaxHeight() {
return this.sheetMaxHeight || this.maxHeight;
}
/**
* Applies the configured bottom sheet opening height.
*/
setSheetHeight() {
const maxHeightInPixels = this.getSheetMaxHeightInPixels();
this.nativeElement.style.maxHeight = maxHeightInPixels + "px";
if (!this.sheetHeight) {
this.nativeElement.style.removeProperty("height");
return;
}
const minHeight = Math.min(this.getSheetMinHeightInPixels(), maxHeightInPixels);
const height = this.resolveCssHeight(this.sheetHeight, maxHeightInPixels);
const clampedHeight = this.clamp(height, minHeight, maxHeightInPixels);
this.nativeElement.style.height = clampedHeight + "px";
}
/**
* Resolves the sheet minimum height in pixels.
* @returns {number} The minimum height.
*/
getSheetMinHeightInPixels() {
const availableHeight = this.getBottomSheetAvailableHeight();
return Math.min(
this.resolveCssHeight(this.sheetMinHeight || "30vh", availableHeight * 0.3),
availableHeight
);
}
/**
* Resolves the sheet maximum height in pixels.
* @returns {number} The maximum height.
*/
getSheetMaxHeightInPixels() {
const availableHeight = this.getBottomSheetAvailableHeight();
return Math.min(
this.resolveCssHeight(this.getSheetMaxHeight(), availableHeight * 0.9),
availableHeight
);
}
/**
* Returns available height for the bottom sheet scope.
* @returns {number} Pixel height that can be used inside the current sheet frame.
*/
getBottomSheetAvailableHeight() {
var _a;
return ((_a = this._bottomSheetScopeFrame) == null ? void 0 : _a.height) || this.getBottomSheetScopeFrame().height || window.innerHeight;
}
/**
* Resolves a CSS height value against the viewport.
* @param {string} value The configured sheet height.
* @param {number} fallback The fallback height in pixels.
* @returns {number} The resolved height.
*/
resolveCssHeight(value, fallback) {
if (!value || value === "auto") return fallback;
const normalizedValue = `${value}`.trim();
const parsedValue = Number.parseFloat(normalizedValue);
if (Number.isNaN(parsedValue)) return fallback;
if (normalizedValue.endsWith("px")) return parsedValue;
if (normalizedValue.endsWith("vh")) return window.innerHeight * parsedValue / 100;
if (normalizedValue.endsWith("vw")) return window.innerWidth * parsedValue / 100;
if (normalizedValue.endsWith("%")) return window.innerHeight * parsedValue / 100;
if (/^-?\d+(\.\d+)?$/.test(normalizedValue)) return parsedValue;
return fallback;
}
/**
* Clamps a number between min and max.
* @param {number} value The measured height.
* @param {number} min The minimum.
* @param {number} max The maximum.
* @returns {number} The clamped value.
*/
clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
/**
* Checks whether an event came from the resize handle.
* @param {Event} e The event to check.
* @returns {boolean} True when the event belongs to the resize handle.
*/
isSheetHandleEvent(e) {
if (!(e == null ? void 0 : e.composedPath)) return false;
return e.composedPath().includes(this.sheetHandleArea);
}
/**
* Checks whether an event happened inside the visible bottom sheet.
* @param {Event} e The event to check.
* @returns {boolean} True when the event coordinates are inside the sheet.
*/
isEventInsideSheet(e) {
if (!this.nativeElement || typeof (e == null ? void 0 : e.clientX) !== "number" || typeof (e == null ? void 0 : e.clientY) !== "number") return false;
const rect = this.nativeElement.getBoundingClientRect();
return e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
}
/**
* Stores the initial drag state for the mobile bottom sheet.
* @param {number|string} pointerId The pointer identifier.
* @param