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.
580 lines (579 loc) • 21.8 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: 9999;\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: 1000;\n }\n}\n\n:host([hide-button]) {\n .close-button {\n display: none;\n }\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";
class SlidingContainer extends WJElement {
/**
* Creates an instance of SlidingContainer.
* @class
*/
constructor() {
super();
__publicField(this, "className", "SlidingContainer");
/**
* 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._resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
if (entry.contentBoxSize) {
if (this.drawingStatus < 3) return;
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 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;
}
/**
* 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"];
}
/**
* 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();
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();
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) {
transparentDiv.style.width = this.maxWidth;
}
let native = document.createElement("div");
native.setAttribute("part", "sliding-container");
native.classList.add("native-sliding-container");
native.style.width = 0;
if (this.hasOpacity) {
native.style.opacity = 0;
}
if (this._isOpen) {
native.style.width = this.maxWidth;
if (this.hasOpacity) {
native.style.opacity = 1;
}
}
if (this.direction === "right") {
native.style.right = 0;
} else {
native.style.left = 0;
}
let slot = document.createElement("slot");
const nativeInner = document.createElement("div");
nativeInner.classList.add("native-sliding-container-inner");
nativeInner.style.width = this.maxWidth;
nativeInner.append(slot);
nativeInner.append(this.htmlCloseButton());
native.append(nativeInner);
wrapperDiv.append(transparentDiv);
wrapperDiv.append(native);
fragment.append(wrapperDiv);
this.transparentDiv = transparentDiv;
this.wrapperDiv = wrapperDiv;
this.nativeElement = native;
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.screenBreakPoint && window.innerWidth <= this.screenBreakPoint) {
this.variant = "over";
}
this.checkForVariant(this.variant);
}
/**
* 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;
}
/**
* 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;
}
/**
* 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 (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";
}
}
}
}
/**
* Executes before the element is opened.
*/
beforeOpen(e) {
}
/**
* Callback function called after the element is opened.
*/
afterOpen(e) {
}
/**
* Executes before closing the element.
*/
beforeClose(e) {
}
/**
* Callback function that is called after the container is closed.
*/
afterClose(e) {
}
/**
* Animates the transition of elements with specified options, toggling the visibility and/or dimensions
* of the associated elements based on their current state.
*
* This method handles both forward and reverse animations for two elements (`transparentDiv` and `nativeElement`)
* with optional opacity changes. It ensures smooth transitioning by canceling any previous animations on the provided
* elements before initiating a new animation sequence.
* @returns {Promise<void>} A promise that resolves when the transition animation is completed.
*/
doAnimateTransition() {
var _a, _b, _c, _d;
const options = {
delay: 0,
endDelay: 0,
fill: "forwards",
duration: +this.animationDuration,
iterationStart: 0,
iterations: 1,
direction: "normal",
easing: this.animationEasing
};
if (this.animation && ((_b = (_a = this.animation) == null ? void 0 : _a.effect) == null ? void 0 : _b.target) !== this.transparentDiv) {
this.animation.cancel();
this.animation = null;
}
if (this.nativeAnimation && ((_d = (_c = this.nativeAnimation) == null ? void 0 : _c.effect) == null ? void 0 : _d.target) !== this.nativeElement) {
this.nativeAnimation.cancel();
this.nativeAnimation = null;
}
if (!this._isOpen) {
if (this.animation && this.nativeAnimation) {
this.animation.reverse();
this.nativeAnimation.reverse();
} else {
this.animation = this.transparentDiv.animate(
[
{
width: 0
},
{
width: this.maxWidth
}
],
options
);
this.nativeAnimation = this.nativeElement.animate(
[
{
...this.hasOpacity ? { opacity: 0 } : {},
width: 0
},
{
...this.hasOpacity ? { opacity: 1 } : {},
width: this.maxWidth
}
],
options
);
}
} else {
if (this.animation && this.nativeAnimation) {
this.animation.reverse();
this.nativeAnimation.reverse();
} else {
this.animation = this.transparentDiv.animate(
[
{
width: this.maxWidth
},
{
width: 0
}
],
options
);
this.nativeAnimation = this.nativeElement.animate(
[
{
...this.hasOpacity ? { opacity: 1 } : {},
width: this.maxWidth
},
{
...this.hasOpacity ? { opacity: 0 } : {},
width: 0
}
],
options
);
}
}
return new Promise((resolve, reject) => {
this.animation.onfinish = () => {
this._isOpen = !this._isOpen;
resolve();
};
});
}
/**
* Opens the sliding container by performing necessary preparatory and transitional operations.
* @param {Event} e The event that triggered the open operation.
* @returns {Promise<void>} A promise that resolves when the open operation, including animations and subsequent handlers, is complete.
*/
async open(e) {
await Promise.resolve(this.beforeOpen(e)).then(async () => {
if (!this._isOpen) {
this.classList.add("open");
event.dispatchCustomEvent(this, "wje-sliding-container:beforeOpen");
this.checkForVariant(this.variant);
await this.doAnimateTransition();
this.syncAria();
await Promise.resolve(this.afterOpen(e)).then(() => {
event.dispatchCustomEvent(this, "wje-sliding-container:open");
});
}
});
}
/**
* Closes the sliding container and performs associated operations such as animations and event dispatches.
* @param {Event} e The event object associated with the close action.
* @returns {Promise<void>} A promise that resolves when the closing operation, including animations and child element removal, is completed.
*/
async close(e) {
await Promise.resolve(this.beforeClose(e)).then(async () => {
if (this._isOpen) {
this.classList.remove("open");
event.dispatchCustomEvent(this, "wje-sliding-container:beforeClose");
await this.doAnimateTransition();
this.syncAria();
await Promise.resolve(this.afterClose(e)).then(() => {
if (this.removeChildAfterClose) {
this.childNodes.forEach((child) => {
child.remove();
});
}
event.dispatchCustomEvent(this, "wje-sliding-container:afterClose");
});
}
});
}
/**
* Toggles the state between open and closed.
* @param {Event} e The event object triggering the toggle.
* @returns {Promise<void>} A promise that resolves once the toggle operation (open or close) is complete.
*/
async toggle(e) {
if (this._isOpen) {
await this.close(event);
} else {
await this.open(event);
}
}
/**
* Cleans up resources associated with the component by disconnecting
* the resize observer and setting it to null.
* @returns {void} Does not return a value.
*/
componentCleanup() {
var _a;
(_a = this._resizeObserver) == null ? void 0 : _a.disconnect();
this._resizeObserver = null;
}
}
SlidingContainer.define("wje-sliding-container", SlidingContainer);
export {
SlidingContainer as default
};
//# sourceMappingURL=wje-sliding-container.js.map