ng-hub-ui-breadcrumbs
Version:
A flexible and reusable breadcrumb component for Angular applications that automatically generates breadcrumbs based on routing configuration
291 lines (280 loc) • 27.7 kB
JavaScript
import * as i0 from '@angular/core';
import { inject, TemplateRef, Directive, InjectionToken, input, ElementRef, signal, afterNextRender, effect, Injectable, contentChild, computed, Component, NgModule, makeEnvironmentProviders } from '@angular/core';
import { NgTemplateOutlet, AsyncPipe } from '@angular/common';
import { Router, ActivatedRoute, NavigationEnd, RouterLink } from '@angular/router';
import { filter, startWith, map } from 'rxjs';
class HubBreadcrumbItemDirective {
template = inject((TemplateRef));
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubBreadcrumbItemDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.8", type: HubBreadcrumbItemDirective, isStandalone: true, selector: "[hubBreadcrumbItem]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubBreadcrumbItemDirective, decorators: [{
type: Directive,
args: [{
selector: '[hubBreadcrumbItem]'
}]
}] });
/**
* Injection token resolving the optional tooltip adapter used by truncated
* breadcrumb labels.
*
* Inject it with `{ optional: true }`; a `null` value means "use the native
* `title` fallback". Register it through {@link provideHubBreadcrumbTooltip}.
*/
const HUB_BREADCRUMB_TOOLTIP_ADAPTER = new InjectionToken('HUB_BREADCRUMB_TOOLTIP_ADAPTER');
/**
* Adds an overflow-aware tooltip to a breadcrumb label element.
*
* Apply `[hubBreadcrumbLabel]` to the link/text of each breadcrumb item. When the
* label is truncated (its rendered text is wider than its box — typically because
* `hub-breadcrumb` truncation is enabled), the directive exposes the full text as
* a tooltip: the native `title` attribute by default, or the richer hub-ui
* tooltip when a {@link HUB_BREADCRUMB_TOOLTIP_ADAPTER} is provided. When the
* label fits, no tooltip is shown.
*/
class HubBreadcrumbLabelDirective {
/**
* Explicit tooltip text. When empty, the host's own text content is used, but
* only while it is truncated.
*/
tooltip = input('', { ...(ngDevMode ? { debugName: "tooltip" } : /* istanbul ignore next */ {}), alias: 'hubBreadcrumbLabel' });
host = inject(ElementRef);
/** Optional hub-ui tooltip adapter; absent means native `title` fallback. */
adapter = inject(HUB_BREADCRUMB_TOOLTIP_ADAPTER, { optional: true });
/** Whether the rendered label is wider than its clipping box. */
isOverflowing = signal(false, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "isOverflowing" }] : /* istanbul ignore next */ []));
/** Trimmed text content of the label, used as the auto tooltip source. */
text = signal('', /* @ts-ignore */
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
/** Becomes true once the browser-only overflow tracking is wired. */
ready = signal(false, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "ready" }] : /* istanbul ignore next */ []));
resizeObserver = null;
mutationObserver = null;
handle = null;
constructor() {
afterNextRender(() => this.initOverflowTracking());
effect(() => {
if (!this.ready()) {
return;
}
const explicit = this.tooltip().trim();
const text = explicit || (this.isOverflowing() ? this.text() : '');
this.applyTooltip(text);
});
}
ngOnDestroy() {
this.resizeObserver?.disconnect();
this.mutationObserver?.disconnect();
this.handle?.destroy();
this.handle = null;
}
/** Wires browser-only observers that keep the truncation state in sync. */
initOverflowTracking() {
const el = this.host.nativeElement;
this.measure(el);
if (typeof ResizeObserver !== 'undefined') {
this.resizeObserver = new ResizeObserver(() => this.measure(el));
this.resizeObserver.observe(el);
}
if (typeof MutationObserver !== 'undefined') {
this.mutationObserver = new MutationObserver(() => this.measure(el));
this.mutationObserver.observe(el, { childList: true, characterData: true, subtree: true });
}
this.ready.set(true);
}
/** Reads the host's text and truncation state into the signals. */
measure(el) {
this.text.set((el.textContent ?? '').trim());
this.isOverflowing.set(el.scrollWidth > el.clientWidth + 1);
}
/**
* Applies the effective tooltip text through the hub-ui adapter when present,
* or the native `title` attribute otherwise. An empty text removes both.
*/
applyTooltip(text) {
const el = this.host.nativeElement;
if (this.adapter) {
el.removeAttribute('title');
if (text) {
if (this.handle) {
this.handle.update(text);
}
else {
this.handle = this.adapter.attach(el, text);
}
}
else if (this.handle) {
this.handle.destroy();
this.handle = null;
}
return;
}
if (text) {
el.setAttribute('title', text);
}
else {
el.removeAttribute('title');
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubBreadcrumbLabelDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.8", type: HubBreadcrumbLabelDirective, isStandalone: true, selector: "[hubBreadcrumbLabel]", inputs: { tooltip: { classPropertyName: "tooltip", publicName: "hubBreadcrumbLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubBreadcrumbLabelDirective, decorators: [{
type: Directive,
args: [{
selector: '[hubBreadcrumbLabel]'
}]
}], ctorParameters: () => [], propDecorators: { tooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubBreadcrumbLabel", required: false }] }] } });
class HubBreadcrumbsService {
#router = inject(Router);
#activatedRoute = inject(ActivatedRoute);
breadcrumbs$ = this.#router.events.pipe(filter((event) => event instanceof NavigationEnd), startWith(undefined), map(() => this.createBreadcrumbs(this.#activatedRoute.root)));
createBreadcrumbs(route, url = '', breadcrumbs = []) {
const children = route.children;
if (children.length === 0) {
return breadcrumbs;
}
for (const child of children) {
const routeSegments = child.snapshot.url;
// Construir la URL para este nivel
const routeURL = routeSegments
.map((segment) => segment.path)
.join('/');
const nextUrl = routeURL ? `${url}/${routeURL}` : url;
// Verificar si esta ruta específica tiene datos de breadcrumb
const hasBreadcrumbData = this.hasOwnBreadcrumbData(child);
if (hasBreadcrumbData) {
const label = this.getBreadcrumbLabel(child);
breadcrumbs.push({
label,
url: nextUrl,
data: child.snapshot.data
});
}
// Continuar con los hijos
return this.createBreadcrumbs(child, nextUrl, breadcrumbs);
}
return breadcrumbs;
}
hasOwnBreadcrumbData(route) {
// Verificar si la ruta tiene su propia configuración de breadcrumb
const routeConfig = route.routeConfig;
return !!(routeConfig &&
routeConfig.data &&
routeConfig.data['breadcrumb']);
}
getBreadcrumbLabel(route) {
const data = route.snapshot.data;
let label = data['breadcrumb'];
if (typeof label === 'function') {
label = label(data);
}
if (data['resolvedData']) {
label = this.getResolvedBreadcrumb(label, data['resolvedData']);
}
return label;
}
getResolvedBreadcrumb(template, data) {
return template.replace(/\{([^}]+)\}/g, (match, key) => {
return data[key] || match;
});
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubBreadcrumbsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubBreadcrumbsService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubBreadcrumbsService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}] });
/** Variants with exact design-system token coverage via the SCSS `@each` loop. */
const BUILT_IN_VARIANTS = new Set(['primary', 'success', 'danger', 'warning', 'info']);
class HubBreadcrumbComponent {
#breadcrumbsSvc = inject(HubBreadcrumbsService);
itemTemplate = contentChild(HubBreadcrumbItemDirective, { ...(ngDevMode ? { debugName: "itemTemplate" } : /* istanbul ignore next */ {}), read: TemplateRef });
breadcrumbs$ = this.#breadcrumbsSvc.breadcrumbs$;
/**
* Semantic accent for the breadcrumb links: `'primary'` · `'success'` ·
* `'danger'` · `'warning'` · `'info'`, or any custom string (read as
* `--hub-sys-color-<variant>`). Re-bases `--hub-breadcrumb-accent`; the
* current (last) item stays muted. Defaults to the standard link colour.
*/
variant = input(/* @ts-ignore */
...(ngDevMode ? [undefined, { debugName: "variant" }] : /* istanbul ignore next */ []));
/**
* Opt-in per-item truncation. When `true`, each label is clipped to
* `--hub-breadcrumb-max-item-width` with an ellipsis and, if it overflows,
* exposes its full text as a tooltip (native `title`, or the hub-ui tooltip
* when {@link provideHubBreadcrumbTooltip} is wired). Off by default, so the
* standard breadcrumb layout is unchanged.
*/
truncateItems = input(false, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "truncateItems" }] : /* istanbul ignore next */ []));
/**
* Inline accent for custom (non-built-in) variants — the built-in five are
* resolved by the SCSS `@each` loop, so this returns `null` for them.
*/
customAccent = computed(() => {
const v = this.variant();
return v && !BUILT_IN_VARIANTS.has(v) ? `var(--hub-sys-color-${v})` : null;
}, /* @ts-ignore */
...(ngDevMode ? [{ debugName: "customAccent" }] : /* istanbul ignore next */ []));
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubBreadcrumbComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.8", type: HubBreadcrumbComponent, isStandalone: true, selector: "hub-breadcrumb", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, truncateItems: { classPropertyName: "truncateItems", publicName: "truncateItems", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.hub-breadcrumb--truncate": "truncateItems()", "attr.data-variant": "variant() ?? null", "style.--hub-breadcrumb-accent": "customAccent()" }, classAttribute: "hub-breadcrumb" }, queries: [{ propertyName: "itemTemplate", first: true, predicate: HubBreadcrumbItemDirective, descendants: true, read: TemplateRef, isSignal: true }], ngImport: i0, template: "<nav aria-label=\"breadcrumb\" class=\"hub-breadcrumb__nav\">\n\t<ol class=\"hub-breadcrumb__list\">\n\t\t@for (item of breadcrumbs$ | async; track item; let last = $last) {\n\t\t\t<li class=\"hub-breadcrumb__item\" [class.hub-breadcrumb__item--active]=\"last\">\n\t\t\t\t@if (itemTemplate()) {\n\t\t\t\t\t<ng-container\n\t\t\t\t\t\t[ngTemplateOutlet]=\"itemTemplate()\"\n\t\t\t\t\t\t[ngTemplateOutletContext]=\"{\n\t\t\t\t\t\t\t$implicit: item,\n\t\t\t\t\t\t\tisLast: last\n\t\t\t\t\t\t}\"\n\t\t\t\t\t/>\n\t\t\t\t} @else {\n\t\t\t\t\t@if (!last) {\n\t\t\t\t\t\t<a [routerLink]=\"item.url\" class=\"hub-breadcrumb__link\" hubBreadcrumbLabel>\n\t\t\t\t\t\t\t{{ item.label }}\n\t\t\t\t\t\t</a>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<span class=\"hub-breadcrumb__text\" hubBreadcrumbLabel aria-current=\"page\">{{ item.label }}</span>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</li>\n\t\t}\n\t</ol>\n</nav>\n", styles: [":root,:host{--hub-breadcrumb-padding-x: var(--hub-ref-space-3, 1rem);--hub-breadcrumb-padding-y: .25rem;--hub-breadcrumb-margin-bottom: var(--hub-ref-space-0, 0);--hub-breadcrumb-bg: var(--hub-sys-surface-page, transparent);--hub-breadcrumb-color: var(--hub-sys-text-primary, #212529);--hub-breadcrumb-border-radius: var(--hub-ref-radius-md, .375rem);--hub-breadcrumb-divider-color: var(--hub-sys-text-muted, #6c757d);--hub-breadcrumb-item-padding-x: var(--hub-ref-space-1, .25rem);--hub-breadcrumb-item-active-color: var(--hub-sys-text-muted, #6c757d);--hub-breadcrumb-accent: var(--hub-sys-color-primary, #0d6efd);--hub-breadcrumb-accent-emphasis: color-mix(in oklch, var(--hub-breadcrumb-accent) 80%, var(--hub-sys-color-ink, #212529));--hub-breadcrumb-accent-subtle: color-mix(in oklch, var(--hub-breadcrumb-accent) 12%, var(--hub-sys-surface-page, #fff));--hub-breadcrumb-accent-on: oklch(from var(--hub-breadcrumb-accent) clamp(0, (.62 - l) * 1000, 1) 0 h);--hub-breadcrumb-link-color: var(--hub-breadcrumb-accent);--hub-breadcrumb-link-hover-color: var(--hub-breadcrumb-accent-emphasis, var(--hub-sys-link-hover-color, #0a58ca));--hub-breadcrumb-link-decoration: none;--hub-breadcrumb-link-hover-decoration: underline;--hub-breadcrumb-divider: \">\";--hub-breadcrumb-divider-flipped: \"<\";--hub-breadcrumb-font-size: inherit;--hub-breadcrumb-max-item-width: 12rem}.hub-breadcrumb__nav .hub-breadcrumb__list{display:flex;flex-wrap:wrap;font-size:var(--hub-breadcrumb-font-size, inherit);padding:var(--hub-breadcrumb-padding-y, .25rem) var(--hub-breadcrumb-padding-x, var(--hub-ref-space-3, 1rem));margin-bottom:var(--hub-breadcrumb-margin-bottom, var(--hub-ref-space-0, 0));list-style:none;color:var(--hub-breadcrumb-color, var(--hub-sys-text-primary, #212529));background-color:var(--hub-breadcrumb-bg, var(--hub-sys-surface-page, transparent));border-radius:var(--hub-breadcrumb-border-radius, var(--hub-ref-radius-md, .375rem))}.hub-breadcrumb__nav .hub-breadcrumb__item+.hub-breadcrumb__item{padding-left:var(--hub-breadcrumb-item-padding-x, var(--hub-ref-space-1, .25rem))}.hub-breadcrumb__nav .hub-breadcrumb__item+.hub-breadcrumb__item:before{float:left;padding-right:var(--hub-breadcrumb-item-padding-x, var(--hub-ref-space-1, .25rem));color:var(--hub-breadcrumb-divider-color, var(--hub-sys-text-muted, #6c757d));content:var(--hub-breadcrumb-divider, \">\")}.hub-breadcrumb__nav .hub-breadcrumb__item--active{color:var(--hub-breadcrumb-item-active-color, var(--hub-sys-text-muted, #6c757d))}.hub-breadcrumb__nav .hub-breadcrumb__link{color:var(--hub-breadcrumb-link-color, var(--hub-sys-link-color, #0d6efd));text-decoration:var(--hub-breadcrumb-link-decoration, none)}.hub-breadcrumb__nav .hub-breadcrumb__link:hover{color:var(--hub-breadcrumb-link-hover-color, var(--hub-sys-link-hover-color, #0a58ca));text-decoration:var(--hub-breadcrumb-link-hover-decoration, underline)}[dir=rtl] .hub-breadcrumb__nav .hub-breadcrumb__item+.hub-breadcrumb__item:before{content:var(--hub-breadcrumb-divider-flipped, var(--hub-breadcrumb-divider, \">\"))}:host(.hub-breadcrumb--truncate) .hub-breadcrumb__nav .hub-breadcrumb__link,:host(.hub-breadcrumb--truncate) .hub-breadcrumb__nav .hub-breadcrumb__text,.hub-breadcrumb--truncate .hub-breadcrumb__nav .hub-breadcrumb__link,.hub-breadcrumb--truncate .hub-breadcrumb__nav .hub-breadcrumb__text{display:inline-block;max-inline-size:var(--hub-breadcrumb-max-item-width, 12rem);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}:host([data-variant=primary]),.hub-breadcrumb[data-variant=primary]{--hub-breadcrumb-accent: var(--hub-sys-color-primary)}:host([data-variant=secondary]),.hub-breadcrumb[data-variant=secondary]{--hub-breadcrumb-accent: var(--hub-sys-color-secondary)}:host([data-variant=success]),.hub-breadcrumb[data-variant=success]{--hub-breadcrumb-accent: var(--hub-sys-color-success)}:host([data-variant=danger]),.hub-breadcrumb[data-variant=danger]{--hub-breadcrumb-accent: var(--hub-sys-color-danger)}:host([data-variant=warning]),.hub-breadcrumb[data-variant=warning]{--hub-breadcrumb-accent: var(--hub-sys-color-warning)}:host([data-variant=info]),.hub-breadcrumb[data-variant=info]{--hub-breadcrumb-accent: var(--hub-sys-color-info)}:host([data-variant=neutral]),.hub-breadcrumb[data-variant=neutral]{--hub-breadcrumb-accent: var(--hub-sys-color-neutral)}:host([data-variant=light]),.hub-breadcrumb[data-variant=light]{--hub-breadcrumb-accent: var(--hub-sys-color-light)}:host([data-variant=dark]),.hub-breadcrumb[data-variant=dark]{--hub-breadcrumb-accent: var(--hub-sys-color-dark)}:host([data-variant]),.hub-breadcrumb[data-variant]{--hub-breadcrumb-accent-emphasis: color-mix(in oklch, var(--hub-breadcrumb-accent) 80%, var(--hub-sys-color-ink, #212529));--hub-breadcrumb-accent-subtle: color-mix(in oklch, var(--hub-breadcrumb-accent) 12%, var(--hub-sys-surface-page, #fff));--hub-breadcrumb-accent-on: oklch(from var(--hub-breadcrumb-accent) clamp(0, (.62 - l) * 1000, 1) 0 h);--hub-breadcrumb-link-hover-color: var(--hub-breadcrumb-accent-emphasis)}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: HubBreadcrumbLabelDirective, selector: "[hubBreadcrumbLabel]", inputs: ["hubBreadcrumbLabel"] }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubBreadcrumbComponent, decorators: [{
type: Component,
args: [{ selector: 'hub-breadcrumb', imports: [AsyncPipe, RouterLink, NgTemplateOutlet, HubBreadcrumbLabelDirective], host: {
class: 'hub-breadcrumb',
'[class.hub-breadcrumb--truncate]': 'truncateItems()',
'[attr.data-variant]': 'variant() ?? null',
'[style.--hub-breadcrumb-accent]': 'customAccent()'
}, template: "<nav aria-label=\"breadcrumb\" class=\"hub-breadcrumb__nav\">\n\t<ol class=\"hub-breadcrumb__list\">\n\t\t@for (item of breadcrumbs$ | async; track item; let last = $last) {\n\t\t\t<li class=\"hub-breadcrumb__item\" [class.hub-breadcrumb__item--active]=\"last\">\n\t\t\t\t@if (itemTemplate()) {\n\t\t\t\t\t<ng-container\n\t\t\t\t\t\t[ngTemplateOutlet]=\"itemTemplate()\"\n\t\t\t\t\t\t[ngTemplateOutletContext]=\"{\n\t\t\t\t\t\t\t$implicit: item,\n\t\t\t\t\t\t\tisLast: last\n\t\t\t\t\t\t}\"\n\t\t\t\t\t/>\n\t\t\t\t} @else {\n\t\t\t\t\t@if (!last) {\n\t\t\t\t\t\t<a [routerLink]=\"item.url\" class=\"hub-breadcrumb__link\" hubBreadcrumbLabel>\n\t\t\t\t\t\t\t{{ item.label }}\n\t\t\t\t\t\t</a>\n\t\t\t\t\t} @else {\n\t\t\t\t\t\t<span class=\"hub-breadcrumb__text\" hubBreadcrumbLabel aria-current=\"page\">{{ item.label }}</span>\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t</li>\n\t\t}\n\t</ol>\n</nav>\n", styles: [":root,:host{--hub-breadcrumb-padding-x: var(--hub-ref-space-3, 1rem);--hub-breadcrumb-padding-y: .25rem;--hub-breadcrumb-margin-bottom: var(--hub-ref-space-0, 0);--hub-breadcrumb-bg: var(--hub-sys-surface-page, transparent);--hub-breadcrumb-color: var(--hub-sys-text-primary, #212529);--hub-breadcrumb-border-radius: var(--hub-ref-radius-md, .375rem);--hub-breadcrumb-divider-color: var(--hub-sys-text-muted, #6c757d);--hub-breadcrumb-item-padding-x: var(--hub-ref-space-1, .25rem);--hub-breadcrumb-item-active-color: var(--hub-sys-text-muted, #6c757d);--hub-breadcrumb-accent: var(--hub-sys-color-primary, #0d6efd);--hub-breadcrumb-accent-emphasis: color-mix(in oklch, var(--hub-breadcrumb-accent) 80%, var(--hub-sys-color-ink, #212529));--hub-breadcrumb-accent-subtle: color-mix(in oklch, var(--hub-breadcrumb-accent) 12%, var(--hub-sys-surface-page, #fff));--hub-breadcrumb-accent-on: oklch(from var(--hub-breadcrumb-accent) clamp(0, (.62 - l) * 1000, 1) 0 h);--hub-breadcrumb-link-color: var(--hub-breadcrumb-accent);--hub-breadcrumb-link-hover-color: var(--hub-breadcrumb-accent-emphasis, var(--hub-sys-link-hover-color, #0a58ca));--hub-breadcrumb-link-decoration: none;--hub-breadcrumb-link-hover-decoration: underline;--hub-breadcrumb-divider: \">\";--hub-breadcrumb-divider-flipped: \"<\";--hub-breadcrumb-font-size: inherit;--hub-breadcrumb-max-item-width: 12rem}.hub-breadcrumb__nav .hub-breadcrumb__list{display:flex;flex-wrap:wrap;font-size:var(--hub-breadcrumb-font-size, inherit);padding:var(--hub-breadcrumb-padding-y, .25rem) var(--hub-breadcrumb-padding-x, var(--hub-ref-space-3, 1rem));margin-bottom:var(--hub-breadcrumb-margin-bottom, var(--hub-ref-space-0, 0));list-style:none;color:var(--hub-breadcrumb-color, var(--hub-sys-text-primary, #212529));background-color:var(--hub-breadcrumb-bg, var(--hub-sys-surface-page, transparent));border-radius:var(--hub-breadcrumb-border-radius, var(--hub-ref-radius-md, .375rem))}.hub-breadcrumb__nav .hub-breadcrumb__item+.hub-breadcrumb__item{padding-left:var(--hub-breadcrumb-item-padding-x, var(--hub-ref-space-1, .25rem))}.hub-breadcrumb__nav .hub-breadcrumb__item+.hub-breadcrumb__item:before{float:left;padding-right:var(--hub-breadcrumb-item-padding-x, var(--hub-ref-space-1, .25rem));color:var(--hub-breadcrumb-divider-color, var(--hub-sys-text-muted, #6c757d));content:var(--hub-breadcrumb-divider, \">\")}.hub-breadcrumb__nav .hub-breadcrumb__item--active{color:var(--hub-breadcrumb-item-active-color, var(--hub-sys-text-muted, #6c757d))}.hub-breadcrumb__nav .hub-breadcrumb__link{color:var(--hub-breadcrumb-link-color, var(--hub-sys-link-color, #0d6efd));text-decoration:var(--hub-breadcrumb-link-decoration, none)}.hub-breadcrumb__nav .hub-breadcrumb__link:hover{color:var(--hub-breadcrumb-link-hover-color, var(--hub-sys-link-hover-color, #0a58ca));text-decoration:var(--hub-breadcrumb-link-hover-decoration, underline)}[dir=rtl] .hub-breadcrumb__nav .hub-breadcrumb__item+.hub-breadcrumb__item:before{content:var(--hub-breadcrumb-divider-flipped, var(--hub-breadcrumb-divider, \">\"))}:host(.hub-breadcrumb--truncate) .hub-breadcrumb__nav .hub-breadcrumb__link,:host(.hub-breadcrumb--truncate) .hub-breadcrumb__nav .hub-breadcrumb__text,.hub-breadcrumb--truncate .hub-breadcrumb__nav .hub-breadcrumb__link,.hub-breadcrumb--truncate .hub-breadcrumb__nav .hub-breadcrumb__text{display:inline-block;max-inline-size:var(--hub-breadcrumb-max-item-width, 12rem);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}:host([data-variant=primary]),.hub-breadcrumb[data-variant=primary]{--hub-breadcrumb-accent: var(--hub-sys-color-primary)}:host([data-variant=secondary]),.hub-breadcrumb[data-variant=secondary]{--hub-breadcrumb-accent: var(--hub-sys-color-secondary)}:host([data-variant=success]),.hub-breadcrumb[data-variant=success]{--hub-breadcrumb-accent: var(--hub-sys-color-success)}:host([data-variant=danger]),.hub-breadcrumb[data-variant=danger]{--hub-breadcrumb-accent: var(--hub-sys-color-danger)}:host([data-variant=warning]),.hub-breadcrumb[data-variant=warning]{--hub-breadcrumb-accent: var(--hub-sys-color-warning)}:host([data-variant=info]),.hub-breadcrumb[data-variant=info]{--hub-breadcrumb-accent: var(--hub-sys-color-info)}:host([data-variant=neutral]),.hub-breadcrumb[data-variant=neutral]{--hub-breadcrumb-accent: var(--hub-sys-color-neutral)}:host([data-variant=light]),.hub-breadcrumb[data-variant=light]{--hub-breadcrumb-accent: var(--hub-sys-color-light)}:host([data-variant=dark]),.hub-breadcrumb[data-variant=dark]{--hub-breadcrumb-accent: var(--hub-sys-color-dark)}:host([data-variant]),.hub-breadcrumb[data-variant]{--hub-breadcrumb-accent-emphasis: color-mix(in oklch, var(--hub-breadcrumb-accent) 80%, var(--hub-sys-color-ink, #212529));--hub-breadcrumb-accent-subtle: color-mix(in oklch, var(--hub-breadcrumb-accent) 12%, var(--hub-sys-surface-page, #fff));--hub-breadcrumb-accent-on: oklch(from var(--hub-breadcrumb-accent) clamp(0, (.62 - l) * 1000, 1) 0 h);--hub-breadcrumb-link-hover-color: var(--hub-breadcrumb-accent-emphasis)}\n"] }]
}], propDecorators: { itemTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => HubBreadcrumbItemDirective), { ...{ read: TemplateRef }, isSignal: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], truncateItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "truncateItems", required: false }] }] } });
class HubBreadcrumbsModule {
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubBreadcrumbsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.8", ngImport: i0, type: HubBreadcrumbsModule, imports: [HubBreadcrumbComponent, HubBreadcrumbItemDirective], exports: [HubBreadcrumbComponent, HubBreadcrumbItemDirective] });
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubBreadcrumbsModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.8", ngImport: i0, type: HubBreadcrumbsModule, decorators: [{
type: NgModule,
args: [{
declarations: [],
imports: [HubBreadcrumbComponent, HubBreadcrumbItemDirective],
exports: [HubBreadcrumbComponent, HubBreadcrumbItemDirective]
}]
}] });
/**
* Registers a tooltip adapter so truncated breadcrumb labels render the rich
* hub-ui tooltip instead of the native `title` fallback.
*
* ```ts
* import { provideHubBreadcrumbTooltip } from 'ng-hub-ui-breadcrumbs';
* import { hubTooltipAdapter } from 'ng-hub-ui-utils';
*
* providers: [provideHubBreadcrumbTooltip(hubTooltipAdapter)];
* ```
*
* @param adapter Tooltip adapter implementation (e.g. `hubTooltipAdapter` from
* `ng-hub-ui-utils`).
* @returns Environment providers to add to the application config.
*/
function provideHubBreadcrumbTooltip(adapter) {
return makeEnvironmentProviders([
{
provide: HUB_BREADCRUMB_TOOLTIP_ADAPTER,
useValue: adapter
}
]);
}
/*
* Public API Surface of breadcrumbs
*/
/**
* Generated bundle index. Do not edit.
*/
export { HUB_BREADCRUMB_TOOLTIP_ADAPTER, HubBreadcrumbComponent, HubBreadcrumbItemDirective, HubBreadcrumbLabelDirective, HubBreadcrumbsModule, HubBreadcrumbsService, provideHubBreadcrumbTooltip };
//# sourceMappingURL=ng-hub-ui-breadcrumbs.mjs.map