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.
771 lines (770 loc) • 34.1 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 { WjElementUtils } from "./element-utils.js";
import { event } from "./event.js";
import "./wje-button.js";
import "./wje-dropdown.js";
import "./icon-CReYMzAK.js";
import "./wje-menu.js";
import "./wje-menu-item.js";
import "./wje-popup.js";
import "./wje-tooltip.js";
const styles = "/*\n[ WJ Breadcrumb ]\n*/\n\n:host {\n display: flex;\n flex: 0 0 auto;\n align-items: center;\n line-height: var(--wje-breadcrumb-line-height, 1.5);\n\n .native-breadcrumb {\n display: flex;\n align-items: center;\n width: 100%;\n outline: none;\n background: inherit;\n margin: var(--wje-breadcrumb-margin, var(--wje-breadcrumb-native-margin, 0));\n padding: var(--wje-breadcrumb-padding, var(--wje-breadcrumb-native-padding, 0.25rem 0.75rem));\n color: var(--wje-breadcrumb-a);\n text-decoration: none;\n font-size: var(--wje-breadcrumb-font-size);\n line-height: var(--wje-breadcrumb-native-line-height, var(--wje-breadcrumb-line-height, 1.5));\n &.hidden {\n display: none;\n }\n &.active {\n font-weight: var(--wje-breadcrumb-active-font-weight);\n font-size: var(--wje-breadcrumb-active-font-size);\n }\n &.disabled {\n cursor: default;\n opacity: 0.6;\n }\n &:hover {\n color: var(--wje-breadcrumb-a-hover);\n }\n }\n\n button {\n margin-inline: 0.75rem;\n border: 0 solid transparent;\n border-radius: 3px;\n background-color: transparent;\n display: flex;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n }\n\n .separator {\n display: inline-flex;\n align-items: center;\n }\n}\n\n:host(.collapsed) {\n display: none;\n}\n\n:host([mobile-collapsed-indicator]) {\n flex: 1 1 auto;\n min-width: 0;\n max-width: 100%;\n overflow: hidden;\n}\n\n.breadcrumb-mobile {\n display: flex;\n align-items: center;\n min-width: 0;\n max-width: 100%;\n color: var(--wje-breadcrumb-a);\n font-size: var(--wje-breadcrumb-font-size);\n line-height: var(--wje-breadcrumb-line-height, 1.5);\n}\n\n.breadcrumb-mobile--text,\n.breadcrumb-mobile--back,\n.breadcrumb-mobile--parent-title {\n width: 100%;\n}\n\n.breadcrumb-mobile--back,\n.breadcrumb-mobile--parent-title {\n flex-direction: column;\n align-items: flex-start;\n gap: 0.125rem;\n}\n\n.breadcrumb-mobile-dropdown,\n.breadcrumb-mobile__menu {\n min-width: 0;\n max-width: 100%;\n}\n\n.breadcrumb-mobile__menu {\n --wje-button-margin-inline: 0;\n --wje-padding-top: 0;\n --wje-padding-bottom: 0;\n --wje-padding-start: 0;\n --wje-padding-end: 0;\n gap: 0.35rem;\n color: var(--wje-breadcrumb-a);\n}\n\n.breadcrumb-mobile__back,\n.breadcrumb-mobile__parent,\n.breadcrumb-mobile__title {\n min-width: 0;\n max-width: 100%;\n overflow: hidden;\n color: inherit;\n font: inherit;\n line-height: inherit;\n text-align: start;\n text-decoration: none;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.breadcrumb-mobile__back,\n.breadcrumb-mobile__parent {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n padding: 0;\n border: 0;\n background: transparent;\n color: var(--wje-breadcrumb-a);\n cursor: default;\n font-size: var(--wje-breadcrumb-mobile-parent-font-size, 0.8125rem);\n opacity: 0.72;\n}\n\nbutton.breadcrumb-mobile__back,\nbutton.breadcrumb-mobile__parent,\nbutton.breadcrumb-mobile__title {\n margin-inline: 0;\n cursor: pointer;\n}\n\nbutton.breadcrumb-mobile__back:hover,\nbutton.breadcrumb-mobile__parent:hover,\nbutton.breadcrumb-mobile__title:hover,\n.breadcrumb-mobile__menu:hover {\n color: var(--wje-breadcrumb-a-hover);\n}\n\n.breadcrumb-mobile__title {\n display: block;\n font-weight: var(--wje-breadcrumb-active-font-weight, 600);\n font-size: var(--wje-breadcrumb-active-font-size, inherit);\n}\n\n.breadcrumb-mobile__label {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.breadcrumb-mobile__icon {\n flex: 0 0 auto;\n}\n\n::slotted([slot='start']) {\n margin-inline: 0 0.5rem;\n}\n\n::slotted([slot='end']) {\n margin-inline: 0.5rem 0;\n}\n";
const MOBILE_COLLAPSED_VARIANTS = /* @__PURE__ */ new Set(["TEXT", "BACK", "PARENT-TITLE", "MENU-TITLE", "SHEET"]);
class Breadcrumb extends WJElement {
/**
* Breadcrumb constructor method.
*/
constructor() {
super();
/**
* Class name for the Breadcrumb element.
* @type {string}
*/
__publicField(this, "className", "Breadcrumb");
this._showSeparator = true;
this._showCollapsedIndicator = false;
this.handleNativeClick = (e) => this.handleDisabledNativeClick(e);
}
/**
* Sets the breadcrumb link URL.
* @param {string|null} value Link URL.
*/
set href(value) {
if (value === null || value === void 0) this.removeAttribute("href");
else this.setAttribute("href", value);
}
/**
* Gets the breadcrumb link URL.
* @returns {string|null}
*/
get href() {
return this.getAttribute("href");
}
/**
* Sets the disabled state.
* @param {boolean} value Disabled state.
*/
set disabled(value) {
if (value === true || value === "" || value === "true" || value === 1 || value === "1") {
this.setAttribute("disabled", "");
} else {
this.removeAttribute("disabled");
}
}
/**
* Gets the disabled state.
* @returns {boolean}
*/
get disabled() {
return this.hasAttribute("disabled");
}
/**
* Get show separator flag.
* @returns {boolean} showSeparator - The show separator flag
*/
get showSeparator() {
return this._showSeparator;
}
/**
* Set show separator flag.
* @param {boolean} value The value to set
*/
set showSeparator(value) {
this._showSeparator = value;
}
/**
* Set collapsed variant.
* @param {string} value The value to set
*/
set collapsedVariant(value) {
this._collapsedVariant = value;
}
/**
* Get collapsed variant.
* @returns {string} The collapsed variant value in uppercase.
*/
get collapsedVariant() {
return this.collapsedVariantName.toUpperCase();
}
/**
* Get collapsed variant token.
* @returns {string}
*/
get collapsedVariantName() {
var _a, _b;
return String(((_a = this.parentElement) == null ? void 0 : _a.collapsedVariant) || ((_b = this.parentElement) == null ? void 0 : _b.variant) || this._collapsedVariant || "button").trim();
}
/**
* Get CSS stylesheet for the Breadcrumb element.
* @static
* @returns {object} styles - The CSS styles
*/
static get cssStyleSheet() {
return styles;
}
/**
* Get observed attributes for the Breadcrumb element.
* @static
* @returns {Array<string>} - The observed attributes array for the Breadcrumb element.
*/
static get observedAttributes() {
return ["show-collapsed-indicator", "collapsed", "last", "href", "target", "rel", "download", "disabled", "title", "aria-label"];
}
/**
* Handles attribute changes for the custom element and updates its behavior or appearance accordingly.
* @param {string} name The name of the attribute that was changed.
* @param {string|null} oldValue The previous value of the attribute before it was changed. Null if the attribute was not previously set.
* @param {string|null} newValue The new value of the attribute after it was changed. Null if the attribute was removed.
*/
attributeChangedCallback(name, oldValue, newValue) {
var _a, _b;
if (oldValue === newValue) return;
if (name === "collapsed") {
const isCollapsed = newValue !== null && WjElementUtils.stringToBoolean(newValue);
this.classList.toggle("collapsed", isCollapsed && !this.hasAttribute("show-collapsed-indicator"));
} else if (name === "show-collapsed-indicator") {
(_a = super.attributeChangedCallback) == null ? void 0 : _a.call(this, name, oldValue, newValue);
const isOn = newValue !== null && WjElementUtils.stringToBoolean(newValue);
this._showCollapsedIndicator = isOn;
this.classList.toggle("collapsed", this.hasAttribute("collapsed") && !isOn);
this.refresh();
} else if (name === "last") {
(_b = super.attributeChangedCallback) == null ? void 0 : _b.call(this, name, oldValue, newValue);
const isLast = newValue !== null && WjElementUtils.stringToBoolean(newValue);
this.active = isLast;
this.showSeparator = !isLast;
this.syncAria();
this.syncNativeAttributes();
this.refresh();
} else {
this.syncAria();
this.syncNativeAttributes();
}
}
/**
* Setup attributes for the Breadcrumb element.
*/
setupAttributes() {
this.isShadowRoot = "open";
this.syncAria();
}
syncAria() {
this.setAriaState({ role: "link" });
if (this.active) this.setAriaState({ current: "page" });
else this.removeAttribute("aria-current");
if (this.disabled) this.setAriaState({ disabled: true });
else this.removeAttribute("aria-disabled");
}
/**
* Draw method for the Breadcrumb element.
* @returns {object} fragment - The document fragment
*/
draw() {
let fragment = document.createDocumentFragment();
let native = document.createElement("a");
native.classList.add("native-breadcrumb");
native.setAttribute("part", "native");
this.native = native;
this.syncNativeAttributes();
let slot = document.createElement("slot");
let start = document.createElement("slot");
start.setAttribute("name", "start");
let end = document.createElement("slot");
end.setAttribute("name", "end");
native.append(start, slot, end);
fragment.append(native);
if (WjElementUtils.stringToBoolean(this._showCollapsedIndicator)) {
fragment.append(this.drawCollapsedIndicator());
native.classList.add("hidden");
}
if (this.shouldRenderSeparator()) {
let separator = document.createElement("span");
separator.classList.add("separator");
separator.setAttribute("part", "separator");
if (WjElementUtils.hasSlot(this, "separator")) {
let slotSeparator = document.createElement("slot");
slotSeparator.setAttribute("name", "separator");
separator.append(slotSeparator);
} else {
separator.innerHTML = `<wje-icon name=${this.separator || "chevron-right"}></wje-icon>`;
}
fragment.append(separator);
}
return fragment;
}
/**
* Returns whether the separator should be visible after this breadcrumb.
* @returns {boolean}
*/
shouldRenderSeparator() {
return this.showSeparator && this.hasVisibleBreadcrumbAfter();
}
/**
* Returns whether any later breadcrumb is still visible in the rendered trail.
* @returns {boolean}
*/
hasVisibleBreadcrumbAfter() {
var _a, _b;
const breadcrumbs = (_b = (_a = this.parentElement) == null ? void 0 : _a.getBreadcrumbs) == null ? void 0 : _b.call(_a);
if (!Array.isArray(breadcrumbs)) return true;
const index = breadcrumbs.indexOf(this);
if (index === -1) return true;
return breadcrumbs.slice(index + 1).some((breadcrumb) => this.isBreadcrumbVisibleInTrail(breadcrumb));
}
/**
* Returns whether a breadcrumb is visible in the rendered trail.
* @param {Element} breadcrumb Breadcrumb element to inspect.
* @returns {boolean}
*/
isBreadcrumbVisibleInTrail(breadcrumb) {
var _a, _b;
return !(((_a = breadcrumb == null ? void 0 : breadcrumb.hasAttribute) == null ? void 0 : _a.call(breadcrumb, "collapsed")) && !((_b = breadcrumb == null ? void 0 : breadcrumb.hasAttribute) == null ? void 0 : _b.call(breadcrumb, "show-collapsed-indicator")));
}
/**
* Synchronizes host attributes to the internal anchor.
*/
syncNativeAttributes() {
var _a;
const native = this.native || ((_a = this.context) == null ? void 0 : _a.querySelector("a.native-breadcrumb"));
if (!native) return;
const isDisabled = this.disabled;
const href = this.href;
if (href !== null && href !== void 0 && !isDisabled) {
native.setAttribute("href", href);
} else {
native.removeAttribute("href");
}
["target", "rel", "download"].forEach((attr) => {
if (this.hasAttribute(attr) && !isDisabled) {
native.setAttribute(attr, this.getAttribute(attr));
} else {
native.removeAttribute(attr);
}
});
["title", "aria-label"].forEach((attr) => {
if (this.hasAttribute(attr)) {
native.setAttribute(attr, this.getAttribute(attr));
} else {
native.removeAttribute(attr);
}
});
native.classList.toggle("active", Boolean(this.active));
native.classList.toggle("disabled", isDisabled);
if (this.active) native.setAttribute("aria-current", "page");
else native.removeAttribute("aria-current");
if (isDisabled) {
native.setAttribute("aria-disabled", "true");
native.setAttribute("tabindex", "-1");
} else {
native.removeAttribute("aria-disabled");
native.removeAttribute("tabindex");
}
}
/**
* Sets up native anchor listeners after render.
*/
afterDraw() {
var _a;
(_a = this.native) == null ? void 0 : _a.addEventListener("click", this.handleNativeClick);
}
/**
* Removes native anchor listeners before redraw/disconnect.
*/
beforeDisconnect() {
var _a;
(_a = this.native) == null ? void 0 : _a.removeEventListener("click", this.handleNativeClick);
}
/**
* Prevents disabled breadcrumbs from navigating or bubbling click handlers.
* @param {MouseEvent} e Click event.
*/
handleDisabledNativeClick(e) {
var _a;
if (!this.disabled) return;
e.preventDefault();
e.stopPropagation();
(_a = e.stopImmediatePropagation) == null ? void 0 : _a.call(e);
}
/**
* Renders the collapsed indicator based on the current collapsed variant.
* If the collapsed variant is 'DROPDOWN', it invokes the collapseDropdown method.
* Mobile breakpoint variants render compact breadcrumb summaries, otherwise the default button is used.
* @returns {any} The rendered collapsed indicator, either as a dropdown or a button.
*/
drawCollapsedIndicator() {
const variant = this.collapsedVariant;
if (variant === "DROPDOWN") {
return this.collapseDropdown();
}
if (this.isMobileCollapsedVariant() && this.isBreakpointMenuIndicator()) {
return this.collapseMobileVariant(variant);
}
return this.collapseButton();
}
/**
* Returns whether the current collapsed variant is a compact mobile layout.
* @returns {boolean}
*/
isMobileCollapsedVariant() {
return MOBILE_COLLAPSED_VARIANTS.has(this.collapsedVariant);
}
/**
* Creates and returns a dropdown UI component for collapsed breadcrumbs.
* This method generates a dropdown element with a button trigger and a menu populated with items corresponding
* to the collapsed breadcrumbs. The dropdown is configured to handle specific interactions and ensure that
* events are appropriately managed to avoid propagation issues. Menu items are linked to their corresponding
* breadcrumbs, enabling the same functionality as clicking on the original breadcrumb.
* @returns {HTMLElement} A configured dropdown element containing a button as trigger and a menu with breadcrumb items.
*/
collapseDropdown() {
const isBreakpointMenuIndicator = this.isBreakpointMenuIndicator();
const breadcrumbs = this.getCollapsedMenuBreadcrumbs(isBreakpointMenuIndicator);
const trigger = this.createCollapsedDropdownTrigger(isBreakpointMenuIndicator);
return this.createBreadcrumbDropdown({
breadcrumbs,
trigger,
placement: isBreakpointMenuIndicator ? "bottom-start" : "bottom"
});
}
/**
* Creates a dropdown containing breadcrumb menu items.
* @param {object} options Configuration for the dropdown shell.
* @param {Array<HTMLElement>} options.breadcrumbs Breadcrumb elements to mirror in the menu.
* @param {HTMLElement} options.trigger Element assigned to the dropdown trigger slot.
* @param {string} options.placement Floating UI placement token for the popup.
* @returns {HTMLElement}
*/
createBreadcrumbDropdown({ breadcrumbs, trigger, placement = "bottom" }) {
const dropdown = document.createElement("wje-dropdown");
dropdown.setAttribute("placement", placement);
dropdown.setAttribute("offset", "10");
const menu = this.createCollapsedBreadcrumbMenu(breadcrumbs);
dropdown.append(trigger, menu);
return dropdown;
}
/**
* Creates the menu used by collapsed dropdown variants.
* @param {Array<HTMLElement>} breadcrumbs Breadcrumb items represented by menu items.
* @returns {HTMLElement}
*/
createCollapsedBreadcrumbMenu(breadcrumbs) {
const menu = document.createElement("wje-menu");
menu.setAttribute("variant", "context");
menu.style.setProperty("--wje-menu-item-justify-content", "flex-start");
menu.addEventListener("click", (e) => {
var _a;
e.stopPropagation();
(_a = e.stopImmediatePropagation) == null ? void 0 : _a.call(e);
});
breadcrumbs.forEach((breadcrumb) => {
const menuItem = document.createElement("wje-menu-item");
this.populateCollapsedMenuItem(menuItem, breadcrumb);
menuItem.__breadcrumb = breadcrumb;
menuItem.addEventListener("wje-menu-item:click", (e) => {
var _a, _b;
(_a = e.preventDefault) == null ? void 0 : _a.call(e);
e.stopPropagation();
(_b = e.stopImmediatePropagation) == null ? void 0 : _b.call(e);
this.forwardBreadcrumbClick(e.currentTarget.__breadcrumb);
});
menu.append(menuItem);
});
return menu;
}
/**
* Returns breadcrumbs that should be exposed in a collapsed menu.
* @param {boolean} isBreakpointMenuIndicator Whether this trigger controls the full breakpoint menu.
* @returns {Array<HTMLElement>}
*/
getCollapsedMenuBreadcrumbs(isBreakpointMenuIndicator) {
var _a, _b;
const breadcrumbs = this.getBreadcrumbs();
if (isBreakpointMenuIndicator) {
return ((_a = breadcrumbs == null ? void 0 : breadcrumbs.getBreadcrumbs) == null ? void 0 : _a.call(breadcrumbs)) || [];
}
return ((_b = breadcrumbs == null ? void 0 : breadcrumbs.getBreadcrumbsCollapsed) == null ? void 0 : _b.call(breadcrumbs)) || [];
}
/**
* Renders one of the compact mobile breakpoint variants.
* @param {string} variant Normalized uppercase variant token.
* @returns {HTMLElement}
*/
collapseMobileVariant(variant) {
if (variant === "TEXT") return this.collapseMobileText();
if (variant === "BACK") return this.collapseMobileBack();
if (variant === "PARENT-TITLE") return this.collapseMobileParentTitle();
if (variant === "MENU-TITLE") return this.collapseMobileMenuTitle();
if (variant === "SHEET") return this.collapseMobileSheet();
return this.collapseButton();
}
/**
* Renders only the current breadcrumb title.
* @returns {HTMLElement}
*/
collapseMobileText() {
const current = this.getCurrentBreadcrumb();
const container = this.createMobileContainer("breadcrumb-mobile--text");
const title = this.createMobileBreadcrumbControl(current, "breadcrumb-mobile__title", {
ariaCurrent: true,
actionable: this.isBreadcrumbActionable(current)
});
container.append(title);
return container;
}
/**
* Renders a parent back action above the current title.
* @returns {HTMLElement}
*/
collapseMobileBack() {
const current = this.getCurrentBreadcrumb();
const parent = this.getParentBreadcrumb();
const container = this.createMobileContainer("breadcrumb-mobile--back");
if (parent) {
const back = this.createMobileBreadcrumbControl(parent, "breadcrumb-mobile__back", {
ariaLabel: "Go to parent",
icon: "arrow-left",
actionable: this.isBreadcrumbActionable(parent)
});
container.append(back);
}
const title = this.createMobileBreadcrumbControl(current, "breadcrumb-mobile__title", {
ariaCurrent: true,
actionable: false
});
container.append(title);
return container;
}
/**
* Renders parent context above the current title.
* @returns {HTMLElement}
*/
collapseMobileParentTitle() {
const current = this.getCurrentBreadcrumb();
const parent = this.getParentBreadcrumb();
const container = this.createMobileContainer("breadcrumb-mobile--parent-title");
if (parent) {
const parentControl = this.createMobileBreadcrumbControl(parent, "breadcrumb-mobile__parent", {
actionable: this.isBreadcrumbActionable(parent)
});
container.append(parentControl);
}
const title = this.createMobileBreadcrumbControl(current, "breadcrumb-mobile__title", {
ariaCurrent: true,
actionable: false
});
container.append(title);
return container;
}
/**
* Renders a menu trigger with the current title.
* @returns {HTMLElement}
*/
collapseMobileMenuTitle() {
const trigger = this.createMobileDropdownTrigger({
icon: "dots",
label: this.getBreadcrumbLabel(this.getCurrentBreadcrumb()),
ariaLabel: "Show breadcrumb menu",
className: "breadcrumb-mobile--menu-title"
});
return this.createMobileDropdown(trigger);
}
/**
* Renders the future sheet variant using the dropdown fallback for now.
* @returns {HTMLElement}
*/
collapseMobileSheet() {
const trigger = this.createMobileDropdownTrigger({
icon: "chevron-down",
iconPosition: "end",
label: this.getBreadcrumbLabel(this.getCurrentBreadcrumb()),
ariaLabel: "Show breadcrumb path",
className: "breadcrumb-mobile--sheet"
});
return this.createMobileDropdown(trigger);
}
/**
* Creates a dropdown fallback for mobile menu-like variants.
* @param {HTMLElement} trigger Element that opens the fallback dropdown.
* @returns {HTMLElement}
*/
createMobileDropdown(trigger) {
const dropdown = this.createBreadcrumbDropdown({
breadcrumbs: this.getTrailBreadcrumbs(),
trigger,
placement: "bottom-start"
});
dropdown.classList.add("breadcrumb-mobile-dropdown");
return dropdown;
}
/**
* Creates a compact mobile dropdown trigger.
* @param {object} options Visual settings for the compact trigger.
* @param {string} options.icon Icon shown next to the title.
* @param {string} options.iconPosition Whether the icon renders at the start or end.
* @param {string} options.label Current breadcrumb label shown beside the icon.
* @param {string} options.ariaLabel Accessible name for the dropdown trigger.
* @param {string} options.className Variant-specific class applied to the trigger.
* @returns {HTMLElement}
*/
createMobileDropdownTrigger({ icon, iconPosition = "start", label, ariaLabel, className }) {
const trigger = document.createElement("wje-button");
trigger.setAttribute("slot", "trigger");
trigger.setAttribute("fill", "link");
trigger.setAttribute("aria-label", ariaLabel);
trigger.classList.add("breadcrumb-mobile", "breadcrumb-mobile__menu", className);
const iconElement = document.createElement("wje-icon");
iconElement.setAttribute("name", icon);
iconElement.setAttribute("slot", iconPosition === "end" ? "end" : "start");
iconElement.classList.add("breadcrumb-mobile__icon");
const title = document.createElement("span");
title.classList.add("breadcrumb-mobile__title");
title.setAttribute("aria-current", "page");
title.textContent = label;
if (label) title.setAttribute("title", label);
if (iconPosition === "end") {
trigger.append(title, iconElement);
} else {
trigger.append(iconElement, title);
}
return trigger;
}
/**
* Creates a wrapper for compact mobile breadcrumb variants.
* @param {string} className Variant class.
* @returns {HTMLElement}
*/
createMobileContainer(className) {
const container = document.createElement("div");
container.classList.add("breadcrumb-mobile", className);
return container;
}
/**
* Creates a text or button control for a breadcrumb.
* @param {HTMLElement|null} breadcrumb Source breadcrumb.
* @param {string} className Class applied to the created control.
* @param {object} options Behavior and accessibility settings for the control.
* @param {boolean} options.actionable Whether clicks should forward to the source breadcrumb.
* @param {boolean} options.ariaCurrent Whether the control marks the current page.
* @param {string} options.ariaLabel Accessible name for action controls.
* @param {string} options.icon Optional icon shown before the label.
* @returns {HTMLElement}
*/
createMobileBreadcrumbControl(breadcrumb, className, { actionable = false, ariaCurrent = false, ariaLabel, icon } = {}) {
const label = this.getBreadcrumbLabel(breadcrumb);
const control = document.createElement(actionable ? "button" : "span");
control.classList.add(className);
if (actionable) {
control.setAttribute("type", "button");
control.addEventListener("click", (e) => {
var _a;
e.preventDefault();
e.stopPropagation();
(_a = e.stopImmediatePropagation) == null ? void 0 : _a.call(e);
this.forwardBreadcrumbClick(breadcrumb);
});
}
if (ariaCurrent) control.setAttribute("aria-current", "page");
if (ariaLabel && actionable) control.setAttribute("aria-label", ariaLabel);
if (label) control.setAttribute("title", label);
if (icon) {
const iconElement = document.createElement("wje-icon");
iconElement.setAttribute("name", icon);
iconElement.classList.add("breadcrumb-mobile__icon");
control.append(iconElement);
}
const text = document.createElement("span");
text.classList.add("breadcrumb-mobile__label");
text.textContent = label;
control.append(text);
return control;
}
/**
* Returns the full trail of sibling breadcrumbs.
* @returns {Array<HTMLElement>}
*/
getTrailBreadcrumbs() {
var _a, _b;
return ((_b = (_a = this.getBreadcrumbs()) == null ? void 0 : _a.getBreadcrumbs) == null ? void 0 : _b.call(_a)) || [];
}
/**
* Returns the current breadcrumb item.
* @returns {HTMLElement|null}
*/
getCurrentBreadcrumb() {
const breadcrumbs = this.getTrailBreadcrumbs();
return breadcrumbs[breadcrumbs.length - 1] || null;
}
/**
* Returns the parent breadcrumb item.
* @returns {HTMLElement|null}
*/
getParentBreadcrumb() {
const breadcrumbs = this.getTrailBreadcrumbs();
return breadcrumbs.length > 1 ? breadcrumbs[breadcrumbs.length - 2] : null;
}
/**
* Returns whether a breadcrumb has a known action to forward.
* @param {HTMLElement|null} breadcrumb Breadcrumb to inspect.
* @returns {boolean}
*/
isBreadcrumbActionable(breadcrumb) {
var _a, _b, _c, _d;
if (!breadcrumb || breadcrumb.disabled || ((_a = breadcrumb.hasAttribute) == null ? void 0 : _a.call(breadcrumb, "disabled"))) return false;
return Boolean(
((_b = breadcrumb.hasAttribute) == null ? void 0 : _b.call(breadcrumb, "href")) || ((_c = breadcrumb.hasAttribute) == null ? void 0 : _c.call(breadcrumb, "onclick")) || breadcrumb.__wjeBreadcrumbManaged || ((_d = breadcrumb.hasAttribute) == null ? void 0 : _d.call(breadcrumb, "data-wje-breadcrumbs-item"))
);
}
/**
* Forwards a compact control or menu item click to the original breadcrumb.
* @param {HTMLElement|null} breadcrumb Source breadcrumb.
*/
forwardBreadcrumbClick(breadcrumb) {
var _a;
if (!breadcrumb) return;
const native = breadcrumb.native || ((_a = breadcrumb.context) == null ? void 0 : _a.querySelector("a.native-breadcrumb"));
if (native && typeof native.click === "function") {
native.click();
} else if (typeof breadcrumb.click === "function") {
breadcrumb.click();
} else {
breadcrumb.dispatchEvent(
new MouseEvent("click", {
bubbles: true,
composed: true,
cancelable: true
})
);
}
}
/**
* Creates the dropdown trigger for collapsed breadcrumbs.
* @param {boolean} isBreakpointMenuIndicator Whether this trigger controls the full breakpoint menu.
* @returns {HTMLElement}
*/
createCollapsedDropdownTrigger(isBreakpointMenuIndicator) {
var _a, _b;
if (isBreakpointMenuIndicator) {
const customTrigger = (_b = (_a = this.getBreadcrumbs()) == null ? void 0 : _a.getBreakpointCollapseTrigger) == null ? void 0 : _b.call(_a);
if (customTrigger) {
const trigger = customTrigger.cloneNode(true);
trigger.setAttribute("slot", "trigger");
if (!trigger.hasAttribute("aria-label")) {
trigger.setAttribute("aria-label", "Show breadcrumb menu");
}
return trigger;
}
}
let button = document.createElement("wje-button");
button.setAttribute("slot", "trigger");
button.setAttribute("fill", "link");
button.setAttribute("aria-label", "Show more breadcrumbs");
button.innerHTML = `<wje-icon name="${this.getCollapsedDropdownIcon(isBreakpointMenuIndicator)}"></wje-icon>`;
return button;
}
/**
* Resolves the icon used by the default dropdown trigger.
* @param {boolean} isBreakpointMenuIndicator Whether this trigger controls the full breakpoint menu.
* @returns {string}
*/
getCollapsedDropdownIcon(isBreakpointMenuIndicator) {
var _a;
if (!isBreakpointMenuIndicator) return "dots";
return ((_a = this.getBreadcrumbs()) == null ? void 0 : _a.breakpointCollapseIcon) || "menu";
}
/**
* Copies breadcrumb content into a collapsed menu item.
* @param {HTMLElement} menuItem Menu item receiving the content.
* @param {HTMLElement} breadcrumb Source breadcrumb.
*/
populateCollapsedMenuItem(menuItem, breadcrumb) {
const nodes = Array.from(breadcrumb.childNodes).map((node) => node.cloneNode(true));
menuItem.replaceChildren(...nodes);
if (menuItem.textContent.trim()) return;
const label = this.getBreadcrumbLabel(breadcrumb);
if (label) {
menuItem.append(document.createTextNode(label));
}
}
/**
* Resolves a readable label for a breadcrumb item.
* @param {HTMLElement|null} breadcrumb Source breadcrumb.
* @returns {string}
*/
getBreadcrumbLabel(breadcrumb) {
if (!breadcrumb) return "";
return this.getCollapsedMenuItemLabel(breadcrumb);
}
/**
* Resolves a readable label for icon-only collapsed menu items.
* @param {HTMLElement} breadcrumb Source breadcrumb.
* @returns {string}
*/
getCollapsedMenuItemLabel(breadcrumb) {
var _a;
const explicitLabel = breadcrumb.getAttribute("aria-label") || breadcrumb.getAttribute("title");
if (explicitLabel == null ? void 0 : explicitLabel.trim()) return explicitLabel.trim();
const text = breadcrumb.textContent.trim();
if (text) return text;
const iconName = (_a = breadcrumb.querySelector('wje-icon[slot="start"]')) == null ? void 0 : _a.getAttribute("name");
if (iconName === "home") return "Home";
const href = breadcrumb.getAttribute("href");
const path = (href == null ? void 0 : href.split(/[?#]/)[0]) || "";
const segment = path.split("/").filter(Boolean).pop();
return segment ? this.humanizeCollapsedMenuItemLabel(segment) : "";
}
/**
* Turns a URL segment into a readable menu label.
* @param {string} value URL segment.
* @returns {string}
*/
humanizeCollapsedMenuItemLabel(value) {
return decodeURIComponent(value).replace(/[-_]+/g, " ").replace(/\b\w/g, (letter) => letter.toUpperCase());
}
/**
* Returns whether this collapsed indicator represents the full breakpoint menu.
* @returns {boolean}
*/
isBreakpointMenuIndicator() {
var _a;
const breadcrumbs = this.parentElement;
return this.hasAttribute("show-collapsed-indicator") && this.hasAttribute("collapsed") && ((_a = breadcrumbs == null ? void 0 : breadcrumbs.isBreakpointMenuCollapseActive) == null ? void 0 : _a.call(breadcrumbs));
}
/**
* Creates a button element that expands hidden breadcrumbs when clicked.
* The button is set with appropriate attributes and event listeners to handle
* the expanding of hidden breadcrumb elements. Clicking the button will remove
* the button itself, reveal hidden breadcrumbs, and stop the current event
* propagation.
* @returns {HTMLButtonElement} The created button configured to expand breadcrumbs.
*/
collapseButton() {
let button = document.createElement("button");
button.setAttribute("aria-label", "Show more breadcrumbs");
button.setAttribute("part", "collapsed-indicator");
button.innerHTML = `<wje-icon name="dots"></wje-icon>`;
event.addListener(button, "click", null, (e) => {
this.native.classList.remove("hidden");
button.remove();
this.getBreadcrumbs().getBreadcrumbsCollapsed().forEach((el) => {
el.classList.remove("collapsed");
});
e.stopPropagation();
});
return button;
}
/**
* Retrieves the breadcrumb trail for the current element by returning its parent element.
* @returns {Element} The parent element representing the breadcrumb trail.
*/
getBreadcrumbs() {
return this.parentElement;
}
}
Breadcrumb.define("wje-breadcrumb", Breadcrumb);
export {
Breadcrumb as default
};
//# sourceMappingURL=wje-breadcrumb.js.map