import * as _angular_core from '@angular/core'; import { Signal, DoCheck, AfterViewInit, AfterViewChecked, OnDestroy, EventEmitter } from '@angular/core'; import * as _lucide_angular from '@lucide/angular'; import { MnCollectionState } from 'mn-angular-lib/collection'; /** * Configuration for a single tab item. */ type MnTabItem = { /** Translation key or label text for the tab. */ label: string; /** * Stable value this tab is written as in the URL while the tab bar mirrors * its selection there (see {@link MnTabDataSource.urlParam}). * * Defaults to a slug of the last segment of {@link label} * (`matches.hub.tab.entrants` → `entrants`, `members.tabMembers` → * `tab-members`). Set it when that slug is not the URL you want to hand out, * or when the label key may be renamed — a shared link is only as stable as * the value in it. */ id?: string; /** Callback invoked when this tab becomes active. */ onClick?: () => void; /** Callback invoked when this tab is deactivated. */ onDeactivate?: () => void; /** Optional notification count shown as a badge on the tab. Accepts a plain number or a Signal. Hidden when 0 or absent. */ badge?: number | Signal; }; /** * Data source configuration for the mn-tab component. */ type MnTabDataSource = { /** List of tab items to display. */ items: MnTabItem[]; /** Index of the tab that should be active by default. */ defaultActive: number; /** * Lifecycle state of the tab set. When {@link MnCollectionState.LOADING} the tab * bar renders a loading skeleton; any other state renders the real tabs. */ state?: MnCollectionState; /** * Number of placeholder tabs to render while {@link state} is LOADING. * Defaults to the number of known `items`, falling back to 3 when no items are * known yet. Set this only when the real tabs are not yet known at load time * (e.g. tabs that depend on data being fetched) to predict the final count. */ skeletonCount?: number; /** * Query parameter the active tab is mirrored in, so a reload or a shared * link lands on the tab the user was looking at. Defaults to `'tab'`; pass * `false` to keep this tab bar out of the URL. * * Each tab bar that can be on screen at the same time as another needs its * own parameter name — two bars sharing one name fight over it. Turn it off * for a tab bar whose selection is not worth sharing, such as one inside a * modal, which would otherwise write to the URL of the page behind it. * * Only user selections are written, so a page nobody has clicked a tab on * keeps a clean URL and falls back to {@link defaultActive}. Restoring a tab * from the URL runs that tab's {@link MnTabItem.onClick} and emits * `activeChange`, so a consumer tracking the active tab itself hears about * the restored one. */ urlParam?: string | false; }; /** * Tab component that renders a horizontal tab bar. * Supports translation keys for labels via MnTranslatePipe. * * The active tab is mirrored in the URL query string by default, so a reload, * a back button or a shared link lands on the same tab; see * {@link MnTabDataSource.urlParam} to rename that parameter or switch it off. */ declare class MnTabComponent implements DoCheck, AfterViewInit, AfterViewChecked, OnDestroy { /** * Router the active tab is written to. Optional: a tab bar used outside a * routed application still works, it just has no URL to mirror into. */ private readonly router; /** Route the tab value is read back from; absent for the same reason as {@link router}. */ private readonly route; /** * Marks the view whenever {@link currentActive} moves. The selection changes from three * places Angular does not notice under OnPush: the query-parameter subscription, the * {@link ngDoCheck} re-resolve, and a stale item being dropped — only a click arrives * through a listener that dirties the view by itself. */ private readonly cdr; /** Watches the URL so a deep link, a back button or an in-app link moves the tab bar. */ private readonly queryParamsSub?; /** * URL keys of the current items, memoised on the items array so a slug is * computed once per tab set rather than on every change-detection pass. */ private urlKeyCache?; /** URL key of {@link currentActive}, so a rebuilt tab set can be recognised as the same tab. */ private currentKey?; /** * Key of a selection whose URL write has not landed yet. Navigation is * asynchronous, so a consumer that rebuilds its tabs in response to the click * can be resolved against a URL that still names the previous tab; until the * write completes, this is the truth about what the user picked. */ private pendingKey?; /** Set on destroy so a deferred restore can't announce a tab nobody is showing. */ private destroyed; /** The horizontally-scrolling wrapper the edge fade is painted onto. */ private scrollContainer?; /** The tab row; queried for the active tab so the indicator can measure it. */ private tabList?; /** The shared underline that slides to the active tab. */ private indicator?; /** Pending indicator remeasure, cancelled on destroy so a post-destroy frame can't read a detached ref. */ private indicatorFrame?; /** * True while a click-initiated slide is animating. The active tab's * `font-bold` widens the row, which fires {@link resizeObserver}; without * this guard the observer's snap ({@link updateIndicator} with `animate: * false`) would land the indicator at its target the same frame the slide * starts, so the transition never paints. Set synchronously in * {@link setActive} — before the frame runs — so the guard doesn't depend on * rAF-vs-ResizeObserver callback ordering. */ private sliding; /** Clears {@link sliding} after the slide finishes; re-armed per click, cancelled on destroy. */ private slidingTimer?; /** Slide duration in ms; matches the indicator's `duration-300` transition. */ private static readonly SLIDE_MS; /** How far the fade reaches in from each overflowing edge. */ private static readonly FADE; /** Data source containing tab items and default active index. */ dataSource: MnTabDataSource; /** * Whether to enable horizontal scrolling when items overflow. * When true, tabs scroll horizontally instead of overflowing their container. * * Defaults to `true`: the tab bar never wraps (`flex-nowrap`), so without * scrolling an overflowing bar would clip its last tabs or push the page width * on narrow screens. When the tabs already fit, `overflow-x-auto` is a no-op, * so this default only ever changes behaviour for the overflow case it fixes. */ scrollable: boolean; /** * Whether tabs should stretch to fill the available width. * Defaults to false, so tabs only take as much space as their content. */ justified: boolean; /** Icons rendered by the template. */ protected readonly icons: Record<"ChevronLeft" | "ChevronRight", _lucide_angular.LucideIconData>; /** * Whether tabs are scrolled out of view past the start edge, which shows the start chevron. * A signal, so a write from the scroll handler or the resize observer marks the view by itself. */ readonly canScrollStart: _angular_core.WritableSignal; /** Whether tabs are hidden past the end edge, which shows the end chevron. */ readonly canScrollEnd: _angular_core.WritableSignal; /** Emits the newly activated tab item whenever the active tab changes. */ activeChange: EventEmitter; /** The currently active tab item. */ currentActive?: MnTabItem; /** Watches the wrapper and the tab row so the fade re-evaluates on width or content changes. */ private resizeObserver?; /** * Whether the tab bar is loading and should render skeleton tabs, from * {@link MnTabDataSource.state}. */ get isLoadingState(): boolean; /** * Index array sizing the loading skeleton: `skeletonCount` when provided, * otherwise the number of known items, falling back to a default when none. */ get skeletonTabs(): number[]; constructor(); /** * Re-resolves the active tab on every change-detection pass. * * The data source is often populated or rebuilt asynchronously (tabs that * depend on fetched data or permissions). Resolving the active tab only once * at init would leave {@link currentActive} pointing at a stale item — the * tab bar would then highlight nothing and swallow the first click — so the * selection is kept in sync with whatever the data source currently holds. */ ngDoCheck(): void; /** * Starts watching the scroll wrapper so the edge fade stays honest. A scroll * moves the fade to whichever side now hides tabs; a resize (viewport change) * or a change to the tab row's width (tabs added, relabelled, skeleton → * loaded) re-checks whether either edge overflows at all. */ ngAfterViewInit(): void; /** Wrapper geometry (`scrollWidth:clientWidth`) at the last fade evaluation. */ private lastFadeGeometry; /** * Re-evaluates the edge fade when the wrapper's scrollable extent changes without its box * changing. {@link resizeObserver} only sees border-box changes, and the tab row is a * block-level flex container that keeps its parent's width while its tabs overflow inside it, * so tabs that arrive after init (permission-gated tabs, badge counts, skeleton → loaded) grow * `scrollWidth` without firing it; until a resize or scroll the fade stayed off. Two property * reads per pass; the repaint only runs when they moved. */ ngAfterViewChecked(): void; ngOnDestroy(): void; /** * Paints a fade over whichever edge has tabs scrolled out of view — a soft * dissolve that reads as "more this way", the affordance a hidden scrollbar * otherwise costs us. Uses a mask (content → transparent) rather than a * background-coloured overlay, so it needs no knowledge of the theme. * * The fade alone only reads as "more" when it dissolves a tab cut off * mid-label. When a tab boundary lands exactly on the edge it merely softens * a complete label and the hidden tabs go unnoticed, so the same overflow * also shows a chevron in the faded strip ({@link canScrollStart}, * {@link canScrollEnd}). */ updateEdgeFades(): void; /** * Scrolls the bar by most of its visible width towards one edge, from a chevron press. The * scroll event that follows moves the fade and hides the chevron whose edge has been reached. * Jumps instead of gliding for a user who asked for reduced motion. * @param direction - -1 towards the start, 1 towards the end. */ scrollTabs(direction: -1 | 1): void; /** * Sets the given tab item as active, invoking deactivate/activate callbacks, * and records the selection in the URL so it survives a reload or a share. * @param item - The tab item to activate. */ setActive(item: MnTabItem): void; /** * Whether a tab is the bar's single Tab stop: the active tab, or the first tab while none is * active yet. Every other tab is reached with the arrow keys, so Tab leaves the bar in one press. * @param item - The tab to check. * @returns True for the one tab that keeps `tabindex="0"`. */ isTabStop(item: MnTabItem): boolean; /** * Keyboard handling on a tab. Left and Right move to the previous or next tab and wrap at the * ends, Home and End jump to the first and last; the tab moved to is activated at once * (automatic activation) and receives focus. Enter and Space activate the focused tab, handled * on keydown so Space does not scroll the page first. Other keys are left alone. * @param event - The keydown on a tab. * @param item - The tab the key was pressed on. */ onTabKeydown(event: KeyboardEvent, item: MnTabItem): void; /** * Moves focus to the tab at `index` and scrolls it into view inside a scrollable bar. Focusing * works before change detection has moved `tabindex="0"` onto it, because a script may focus an * element with `tabindex="-1"`. * @param index - Position of the tab in the data source's items. */ private focusTab; /** * Moves the selection to `item` and tells the consumer about it: the * deactivate/activate/emit sequence a click produces, shared by the click * path and the URL-driven ones (deep link, back button), which owe the * consumer the same notifications. * @param item - The tab item to activate. */ private activate; /** * Marks a click-driven slide as in progress and schedules the guard to lift * once the transition has finished. A timeout (not `transitionend`) so the * flag still clears under `motion-reduce`, where no transition event fires. */ private beginSlide; /** * Moves the shared underline to the active tab. When `animate` is false the * move is snapped (no slide) by disabling the transition for one reflow — * used on init, async selection and resize, where a slide would read as jank. * @param animate - Whether the move should slide (true) or snap (false). */ private updateIndicator; /** * Remeasures the indicator on the next animation frame, so the read happens * after layout reflects the latest active-tab classes. Coalesces bursts and * is cancellable on destroy. * @param animate - Whether the resulting move should slide. */ private scheduleIndicator; /** * Returns the resolved badge value for a tab item, supporting both plain numbers and Signal. * @param item - The tab item whose badge to resolve. */ getBadge(item: MnTabItem): number | undefined; /** * Ensures {@link currentActive} references a tab that still exists in the data * source, preferring the tab named in the URL and falling back to the * configured default tab when the current selection is missing or stale * (e.g. after the items array is replaced). */ private syncActiveTab; /** * Records `item` as the selection, remembering its URL key so the same tab is * recognised after the consumer rebuilds the items array. * @param item - The newly selected tab. */ private select; /** * Runs the restored tab's callbacks a change-detection pass later, unless the * selection moved on in the meantime (a click, or another tab set arriving). * @param item - The tab restored from the URL. */ private announceRestored; /** * Activates the tab a URL value names, when it is not the tab already on * screen. Values naming no known tab are ignored: another tab bar on the page * may own that parameter, and a stale link should leave the default standing. * @param key - The value read from the query parameter, if any. */ private activateUrlKey; /** * The query parameter this tab bar mirrors into, or undefined when there is * nothing to mirror into (no router) or the consumer switched it off. */ private urlParam; /** * The tab the current URL asks for, if it names one of `items`. * @param items - The tab set to resolve the URL value against. */ private itemFromUrl; /** * Records the active tab in the query string, replacing the current history * entry: switching tabs is not a navigation to walk back through, and back * should leave the page rather than retrace its tabs. * @param item - The tab that just became active. */ private writeUrl; /** * The URL key of every tab, in item order: the item's `id`, else a slug of * its label. Repeats are numbered so each tab still round-trips through the * URL; give such tabs an explicit `id` to choose the value yourself. * @param items - The tab set to key. */ private urlKeys; static ɵfac: _angular_core.ɵɵFactoryDeclaration; static ɵcmp: _angular_core.ɵɵComponentDeclaration; } export { MnTabComponent }; export type { MnTabDataSource, MnTabItem };