UNPKG

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
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