import * as i0 from '@angular/core'; import { TemplateRef, OnInit, OnDestroy, DoCheck, ChangeDetectorRef, ElementRef, EventEmitter, OnChanges } from '@angular/core'; import * as _lucide_angular from '@lucide/angular'; import { LucideIconData } from '@lucide/angular'; import { BehaviorSubject } from 'rxjs'; import { MnLanguageService } from 'mn-angular-lib/core'; import { MnSelectOption, MnActionIcon, MnDropdownActionColor, MnMultiSelectOption, MnDropdownAction } from 'mn-angular-lib/forms'; import { MnSkeletonProps } from 'mn-angular-lib/button'; import { MnBottomSheet } from 'mn-angular-lib/bottom-sheet'; type PaginationStrategy = { hasMoreRows: boolean; loadMore: () => Promise; reset?: () => void; }; type CursorPaginationStrategy = { endCursor?: string; } & PaginationStrategy; type OffsetPaginationStrategy = { currentPage: number; pageSize: number; totalItems?: number; } & PaginationStrategy; /** * Lifecycle state of a collection's data, driving which chrome the component * renders: skeleton placeholders ({@link LOADING}), the rows or empty state * ({@link RETRIEVED}), or an error placeholder ({@link ERROR}). * * Because the components are zoneless and OnPush, a consumer that flips `state` * to `ERROR` (or `RETRIEVED`) must also emit on `dataRows` (e.g. `dataRows.next([])`) * so the component runs change detection and re-reads the new state. */ declare enum MnCollectionState { /** Data is being (re)loaded; skeleton placeholders are shown. */ LOADING = "LOADING", /** Data has loaded (possibly empty); rows or the empty state are shown. */ RETRIEVED = "RETRIEVED", /** Loading failed; the error placeholder is shown. */ ERROR = "ERROR" } type PaginationMode = 'none' | 'load-more' | 'paginated' | 'client-side-pagination' | 'infinite-scroll'; type MnCollectionLabels = { loadMore?: string; /** Translation key for the "Load more" button label. */ loadMoreKey?: string; rowsPerPage?: string; /** Translation key for the "Rows per page" label. */ rowsPerPageKey?: string; /** * Page position readout, shown on narrow viewports where the item range does * not fit. Supports the `{{current}}` and `{{total}}` placeholders. * Defaults to `Page {{current}} of {{total}}`. */ pageIndicator?: string; /** Translation key for the page position readout. */ pageIndicatorKey?: string; /** * Item range readout. Supports the `{{start}}`, `{{end}}` and `{{total}}` * placeholders. Defaults to `{{start}}–{{end}} of {{total}}`. */ itemRange?: string; /** Translation key for the item range readout. */ itemRangeKey?: string; }; /** * Chrome shared by every MnLib collection component (table, list, grid): * data, search, pagination, loading/skeleton, empty state and i18n. Component * data sources ({@link import('../mn-table').TableDataSource}, * {@link import('../mn-list').ListDataSource}, * {@link import('../mn-grid').GridDataSource}) extend this with their own * rendering contract (columns / item template / card template). */ type MnCollectionDataSource = { dataRows: BehaviorSubject; getID: (row: T) => string; emptyMessage: string; /** Translation key for the empty message. When set, the component resolves it via MnLanguageService. */ emptyMessageKey?: string; emptyTemplate?: TemplateRef; /** * Icon rendered above {@link emptyMessage} in the default empty state. Pass any * lucide icon's static `.icon` data (e.g. `LucideSearchX.icon`). Defaults to an * inbox icon when omitted; set to `null` to render the message with no icon. * Ignored when {@link emptyTemplate} is provided. */ emptyIcon?: LucideIconData | null; /** * Lifecycle state of the data, controlling loading / error / empty rendering. * Defaults to {@link MnCollectionState.RETRIEVED} when not set. */ state?: MnCollectionState; /** Number of placeholder rows rendered while data is loading. Defaults to 5. */ skeletonRowCount?: number; /** Message shown in the error placeholder when {@link state} is ERROR. */ errorMessage?: string; /** Translation key for {@link errorMessage}; resolved via MnLanguageService. */ errorMessageKey?: string; /** Custom template rendered in place of the default error placeholder. */ errorTemplate?: TemplateRef; /** * Whether to show the search box in the toolbar. When omitted, search auto-enables once * the collection holds at least {@link searchThreshold} rows *and* a way to search exists * ({@link isInSearch} or {@link onServerSearch}) — the same rule mn-select and * mn-multi-select apply to their option lists, so long collections stay filterable * without every call site opting in. Set explicitly to force it on or off. */ canSearch?: boolean; /** * Number of rows at which the search box auto-enables (default: 8). Ignored when * {@link canSearch} is set explicitly. Server-paginated sources count {@link totalItems}. */ searchThreshold?: number; searchPlaceholder?: string; /** Translation key for the search placeholder. When set, the component resolves it via MnLanguageService. */ searchPlaceholderKey?: string; isInSearch?: (row: T, searchValue: string) => boolean; searchForAdditionalItems?: (searchValue: string) => Promise; /** * Callback invoked when the user types in the search box (server-side search). * When provided, the component skips client-side filtering and delegates to the consumer. */ onServerSearch?: (searchValue: string) => void; paginationMode?: PaginationMode; paginationStrategy?: PaginationStrategy; loadAdditionalRows?: () => Promise; /** Number of items per page when paginationMode is 'paginated'. Defaults to 10. */ pageSize?: number; /** Options for the page-size selector dropdown. Defaults to [5, 10, 25, 50]. */ pageSizeOptions?: number[]; /** Callback invoked when the user changes the page size via the dropdown. */ onPageSizeChange?: (newSize: number) => void; /** * Total number of items on the server. * When set, pagination and infinite-scroll use this instead of filteredItems.length. */ totalItems?: number; /** * Callback invoked when the user navigates to a different page. * When provided, the component delegates pagination to the consumer (server-side). */ onPageChange?: (page: number) => void; /** * Callback invoked when the user scrolls to the bottom in infinite-scroll mode. * When provided, the component delegates loading more rows to the consumer (server-side). */ onLoadMore?: () => void; labels?: MnCollectionLabels; }; /** * Adds row/item selection to {@link MnCollectionDataSource}. Used by components * that support selection (table, list); grid intentionally omits it. */ type MnSelectableCollectionDataSource = MnCollectionDataSource & { selectionMode?: 'none' | 'single' | 'multi'; selectedRows?: BehaviorSubject; /** IDs to pre-select when the component initializes. */ initialSelectedIds?: string[]; /** * Rows behind {@link initialSelectedIds}, for collections whose rows are paged in * from a server. The component can only recognise a selected row once it has been * loaded, so on page 1 of a server-paginated table it knows the *ids* that are * selected but not what they are called — which is exactly what * {@link selectionSummary} needs to render. Supplying the rows here fills that * gap. Unnecessary when every row is client-side: the ids resolve against * `dataRows` on their own. */ initialSelectedRows?: T[]; /** * Renders an always-visible summary of the current selection above the * collection: a count and one removable tag per selected row. * * It exists because a selection and a paginated list answer different questions. * The list is for *finding* rows and is therefore filtered, searched and paged; * the selection is the answer the user is assembling, and hiding it on page 40 * makes people re-pick rows they already had. The summary never pages, filters or * sorts — it always shows the whole selection. */ selectionSummary?: boolean; /** * Label for a row inside {@link selectionSummary}. Defaults to the first column * that renders a plain string, falling back to the row's id. */ selectionLabel?: (row: T) => string; /** * How many tags {@link selectionSummary} shows before collapsing the rest behind * a "+N more" control. Defaults to 8. * * A summary exists to be taken in at a glance, so it must not grow without bound: * left uncapped, selecting a few hundred rows turns the header into the page and * pushes the table — the thing being worked in — off screen entirely. The count in * the heading always states the true total, so collapsing hides tags, never * information. */ selectionSummaryLimit?: number; /** Labels for the {@link selectionSummary} chrome. */ selectionSummaryLabels?: { /** Heading, supporting a `{{count}}` placeholder. Defaults to `Selected ({{count}})`. */ title?: string; /** Translation key for {@link title}. */ titleKey?: string; /** Label for the clear-everything action. Defaults to `Clear all`. */ clearAll?: string; /** Translation key for {@link clearAll}. */ clearAllKey?: string; /** Accessible label for a tag's remove button, supporting `{{label}}`. */ remove?: string; /** Translation key for {@link remove}. */ removeKey?: string; /** Expand action, supporting a `{{count}}` placeholder. Defaults to `+{{count}} more`. */ showMore?: string; /** Translation key for {@link showMore}. */ showMoreKey?: string; /** Collapse action. Defaults to `Show less`. */ showLess?: string; /** Translation key for {@link showLess}. */ showLessKey?: string; }; }; /** * Shared chrome for MnLib collection components (table, list, grid): * data subscription, client/server search, every pagination mode, load-more, * skeleton-row count, empty-state plumbing, common i18n key resolution and * toolbar change-detection. * * Concrete components extend this (or {@link MnSelectableCollectionBase}) and * implement only their rendering. The class is decorated `@Directive()` so it can * declare `@Input`s and use `inject()` while remaining abstract. * * Init runs in a fixed order (see {@link ngOnInit}); subclasses hook in via the * `protected` template methods rather than overriding `ngOnInit`. */ declare abstract class MnCollectionBase> implements OnInit, OnDestroy, DoCheck { dataSource: DS; /** Row count at which the search box auto-enables when `canSearch` is unset. */ private static readonly DEFAULT_SEARCH_THRESHOLD; filteredItems: T[]; paginatedItems: T[]; searchValue: string; loadingMoreRows: boolean; /** Fallback empty-state icon used when a data source doesn't set `emptyIcon`. */ protected readonly defaultEmptyIcon: _lucide_angular.LucideIconData; currentPage: number; pageSize: number; /** * Measured pixel height of the body container, applied as a `min-height` floor * while a server reload is in flight so the container can't collapse when the * data rows are swapped for skeletons. Released in {@link ngDoCheck} the moment * the loading state clears. `0` means no lock. */ lockedMinHeight: number; /** * Measured pixel height of one full page, applied as a persistent `min-height` floor * while paginated so a short page (the last page, or after a row is removed/filtered) * can't collapse the body and jump the layout below it. Blank space fills the remaindernpm * at the bottom. Captured once a full page is actually on screen; `0` means unmeasured. * Distinct from the transient {@link lockedMinHeight} reload lock — both combine in * {@link bodyMinHeight} via `Math.max`. */ fullPageHeight: number; /** Write-once guard so {@link fullPageHeight} is measured once per pageSize. */ private pageHeightMeasured; protected readonly cdr: ChangeDetectorRef; protected readonly lang: MnLanguageService; /** Prefix used in validation error messages, e.g. `MnList`. Overridden by subclasses. */ protected readonly componentName: string; private dataSubscription?; private searchSubject; private searchSubscription?; private langSubscription?; /** Tracks the previous toolbar template reference for change detection. */ private previousToolbarTemplate?; constructor(); /** * Single source of truth for the data lifecycle: the explicit * {@link MnCollectionDataSource.state}, defaulting to RETRIEVED when unset. * Every internal loading check routes through this. */ get collectionState(): MnCollectionState; /** Whether the collection is currently loading (skeleton placeholders shown). */ get isLoadingState(): boolean; /** Accessible name for the loading placeholder, and the text the status region announces. */ get loadingLabel(): string; /** Whether loading failed (the error placeholder is shown instead of rows/empty). */ get isErrorState(): boolean; /** Whether the component delegates search to the consumer (server-side). */ get isServerSearched(): boolean; /** * Whether the search box is shown: the explicit `canSearch` when set, otherwise * auto-enabled once the row count reaches `searchThreshold` and the source can actually * search (a client predicate or a server callback — a box that filters nothing is noise). * A non-empty term keeps the box even when the (server-)filtered result drops below the * threshold, so the user can always clear what they typed. */ get isSearchable(): boolean; get isPaginated(): boolean; /** Whether the component delegates pagination to the consumer (server-side). */ get isServerPaginated(): boolean; get showLoadMore(): boolean; /** * The measured full-page {@link fullPageHeight} floor, but only while it should apply: * paginated, not loading, with rows present. Keeps a short page from collapsing the body. */ get reservedPageHeight(): number; /** * `min-height` (px) applied to the body: the larger of the transient reload lock and the * persistent full-page floor, so neither can shrink the body below the other. `null` clears it. */ get bodyMinHeight(): number | null; /** Total number of items, accounting for server-side pagination. */ get totalItemCount(): number; get totalPages(): number; get resolvedPageSizeOptions(): number[]; /** Page-size options formatted for mn-select. */ get pageSizeSelectOptions(): MnSelectOption[]; get visiblePages(): number[]; /** * Body container wrapping the skeleton/data swap region, used to measure its * height for {@link lockBodyHeight}. Implemented by each component with a * `@ViewChild('collectionBody')` so the template reference resolves there. */ protected abstract collectionBody?: ElementRef; /** The toolbar template whose identity is watched in change detection. */ protected abstract get trackedToolbarTemplate(): TemplateRef | undefined; get skeletonRows(): number[]; ngOnInit(): void; ngDoCheck(): void; ngOnDestroy(): void; onSearch(searchString: string): void; goToPage(page: number): void; onPageSizeChange(newSize: number): void; loadMoreRows(): void; isTemplateRef(value: unknown): value is TemplateRef; trackByID: (_index: number, item: T) => string; /** Applies search/sort/filtering and pagination to the current rows. */ protected abstract applyFilter(searchForItems: boolean): void; /** Runs after pageSize is set but before the first {@link applyFilter}. */ protected beforeInitialFilter(): void; /** * Runs after a fresh batch of rows has been filtered in, for work that needs the * rows to exist. Subclasses override to react to data arriving late; call `super` * to keep the default (currently nothing). */ protected onRowsChanged(): void; /** * Resolves a label three ways, in order: the consumer's explicit key, a * conventional `mnCollection.*` key when the app defines one, and finally a * readable English default. * * The middle step is what makes the components translatable out of the box: an * app that adds the `mnCollection` namespace to its locale files gets every * table, list and grid translated at once, with no per-call-site wiring across * dozens of data sources. An app that does not keeps today's English text rather * than leaking raw keys into the UI. * * @param consumerKey The data source's own translation key, if it set one. * @param defaultKey The conventional key this label falls back to. * @param fallback The English text used when neither key resolves. * @param params Optional interpolation values. * @returns The resolved label. */ protected resolveLabel(consumerKey: string | undefined, defaultKey: string, fallback: string, params?: Record): string; /** * Resolves translation keys to display strings via {@link MnLanguageService}. * Subclasses override to resolve their own keys; call `super` to keep these. */ protected resolveTranslationKeys(): void; /** * Captures the body container's current height into {@link lockedMinHeight} so it * holds while a server reload swaps the data rows for skeletons. Must be called * while the old rows are still rendered (before delegating to the consumer), and * only locks when rows are present — the first load has nothing to preserve. */ protected lockBodyHeight(): void; /** * Drops the cached {@link fullPageHeight} floor so it is re-measured on the next full page. * Must run whenever the page size changes (the old floor is for a different row count). */ protected invalidatePageHeight(): void; protected applyPagination(): void; /** Client-side search filtering shared by list and grid. */ protected applySearchFilter(items: T[]): T[]; protected processLoadedRows(rows: T[]): void; /** * Reports every misconfigured pagination setting and repairs it in place. * * This deliberately does **not** throw. It runs first in {@link ngOnInit}, and a * throw there aborts the rest of init — the data subscription is never made and * {@link applyFilter} never runs, so the component renders a permanently empty * body that only "heals" once some later interaction happens to call * {@link applyFilter}. That failure mode reads as "the table is broken" rather * than "the data source is misconfigured", and inside a modal the thrown error * is easy to miss entirely. Logging loudly and degrading to the nearest working * mode keeps the misconfiguration visible while still rendering the rows. */ protected normalizeDataSource(): void; /** * Logs a data-source configuration problem, prefixed with the component name. * @param message What is wrong and how it was compensated for. */ private reportConfigError; static ɵfac: i0.ɵɵFactoryDeclaration, never>; static ɵdir: i0.ɵɵDirectiveDeclaration, never, never, { "dataSource": { "alias": "dataSource"; "required": false; }; }, {}, never, never, true, never>; } /** * Extends {@link MnCollectionBase} with single/multi row selection, shared by * components that support it (table, list). Grid extends the plain base instead. */ declare abstract class MnSelectableCollectionBase> extends MnCollectionBase { selectionChange: EventEmitter; selectedIds: Set; /** Whether the summary is currently showing every tag rather than the first few. */ selectionSummaryExpanded: boolean; get allSelected(): boolean; /** * The row behind every selected id, kept so the selection survives the rows * themselves going away. * * {@link selectedIds} alone is enough to tick a checkbox, because that only ever * asks about a row already on screen. The summary asks the opposite question — * "what is selected, including what isn't on this page?" — and a server-paginated * collection has long since discarded those rows. Rows are captured as they are * selected and backfilled from {@link MnSelectableCollectionDataSource.initialSelectedRows} * and from each batch that loads. */ protected selectedRowsById: Map; /** * Whether a seeded initial selection still has to be announced, because none of * its ids matched a loaded row yet. See {@link beforeInitialFilter}. */ private pendingInitialEmit; /** * Every selected row, in selection order, for the summary. Ids whose row was * never seen are skipped rather than rendered as a bare id. */ get selectedSummaryRows(): T[]; /** Whether the selection summary should render. */ get showSelectionSummary(): boolean; /** How many tags to show before collapsing the remainder. */ get selectionSummaryLimit(): number; /** * Tag count the summary collapses at when the data source names no limit. * * Subclasses that know their own width narrow this: the same eight tags that * read as a compact header on a wide table become seven stacked lines in a phone * sheet, pushing the rows they describe off screen. Overridden by * {@link MnCollectionDataSource.selectionSummaryLimit}. */ protected get defaultSelectionSummaryLimit(): number; /** * The tags to render: the first {@link selectionSummaryLimit} rows, or all of them * once expanded. Keeps a large selection from turning the header into the page. */ get visibleSelectionRows(): T[]; /** How many selected rows are collapsed out of view; 0 when all are shown. */ get hiddenSelectionCount(): number; /** Expands or re-collapses the summary's tag list. */ toggleSelectionSummary(): void; /** * The label for a row in the summary: the consumer's {@link * MnSelectableCollectionDataSource.selectionLabel}, else the first column that * renders a plain string, else the row's id. * @param row The selected row. * @returns The text to show on the row's tag. */ selectionLabelFor(row: T): string; /** Removes one row from the selection, from its tag in the summary. */ removeSelection(row: T): void; get hasSelection(): boolean; get isMultiSelect(): boolean; isSelected(item: T): boolean; /** Clears the whole selection, from the summary's clear-all action. */ clearSelection(): void; toggle(item: T): void; toggleAll(): void; /** * Fallback label used when the data source declares no `selectionLabel`. * Subclasses that know how to render a row as text (a table knows its columns) * override this; the base has nothing to go on. * @returns The label, or null when none can be derived. */ protected defaultSelectionLabel(_row: T): string | null; /** Seeds selection from `initialSelectedIds` before the first filter pass. */ protected beforeInitialFilter(): void; /** Announces a deferred initial selection as soon as its rows are loaded. */ protected onRowsChanged(): void; protected emitSelection(): void; /** Records the row object for every loaded row that is currently selected. */ private captureSelectedRows; /** The currently loaded rows whose id is selected. */ private resolveSelectedRows; static ɵfac: i0.ɵɵFactoryDeclaration, never>; static ɵdir: i0.ɵɵDirectiveDeclaration, never, never, {}, { "selectionChange": "selectionChange"; }, never, never, true, never>; } /** One position in the page-number strip. */ type MnPageSlot = { /** Page to jump to, or `null` for an ellipsis gap. */ page: number | null; /** * True for the first/last page anchors and the gaps beside them. These are * hidden below `md`, where the readout states the total and « » already jump * to either end — the strip would otherwise wrap. */ anchor: boolean; }; /** * Presentational pagination footer shared by every MnLib collection component * (table, list, grid): the load-more button, the page-size selector and the * page navigator. It holds no state — the host component owns pagination state * (via {@link import('./mn-collection-base.directive').MnCollectionBase}) and * reacts to the outputs. */ declare class MnCollectionPagination { private readonly lang; /** Prefix for the page-size select's id, keeping it unique per host. */ idPrefix: string; isPaginated: boolean; isServerPaginated: boolean; showLoadMore: boolean; loadingMoreRows: boolean; currentPage: number; pageSize: number; totalPages: number; totalItemCount: number; visiblePages: number[]; pageSizeSelectOptions: MnSelectOption[]; labels?: MnCollectionLabels; loadMore: EventEmitter; pageChange: EventEmitter; pageSizeChange: EventEmitter; get showPagination(): boolean; /** First item number on the current page, 1-based. Zero when there is no data. */ get rangeStart(): number; /** Last item number on the current page, clamped to the total. */ get rangeEnd(): number; /** * {@link visiblePages} anchored with the first and last page, so the total page * count is on screen at md+ without consulting the readout. * * e.g. page 5 of 50 → `1 … 4 5 6 … 50` */ get pageSlots(): MnPageSlot[]; /** e.g. `Page 5 of 50`. */ get pageIndicatorLabel(): string; /** e.g. `41–50 of 250`. */ get itemRangeLabel(): string; /** "Items per page" label beside the page-size selector. */ get rowsPerPageLabel(): string; /** * Substitutes `{{name}}` placeholders, matching the interpolation syntax used * by MnLanguageService so the same translation strings work either way. */ private fill; /** Label for the load-more button. */ get loadMoreLabel(): string; /** Accessible label for the first-page control. */ get firstPageLabel(): string; /** Accessible label for the previous-page control. */ get previousPageLabel(): string; /** Accessible label for the next-page control. */ get nextPageLabel(): string; /** Accessible label for the last-page control. */ get lastPageLabel(): string; /** * Wrapper classes for one slot in the page strip, shrinking it in two steps as * the footer narrows. Container queries, so the measurement is the footer's own * width — the same strip is wide on a page and cramped in a modal. * * The first/last anchors and their gaps drop below 640px, where « and » already * jump to either end. Below 380px every number except the current one drops too: * the strip would otherwise wrap onto a second line and push the footer over the * table, and the "Page 3 of 9" readout beside it already says where the user is. * The arrows survive both steps, so navigation never depends on a number. */ slotVisibility(slot: MnPageSlot): string; /** * Accessible label for a page-number button. * @param page The page the button jumps to. * @returns The label, naming the page. */ pageLabel(page: number): string; /** * Resolves a label three ways, in order: the consumer's explicit text, the * conventional `mnCollection.*` key when the app defines one, and finally a * readable English default. * * Mirrors `MnCollectionBase.resolveLabel`; this component is presentational and * does not extend that base, but its chrome must be just as translatable — the * page-size label and the item-range readout are on screen for every paged * collection in the app. * * @param explicit The label the host passed in, if any. * @param key The conventional translation key to try. * @param fallback The English text used when neither resolves. * @returns The resolved label. */ private label; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } declare enum ColumnSortType { ALPHABETICAL = "ALPHABETICAL", NUMERICAL = "NUMERICAL", DATE = "DATE", NONE = "NONE" } type SortState = { columnKey: string; direction: 'asc' | 'desc'; }; type TableAppearance = { striped?: boolean; hover?: boolean; compact?: boolean; bordered?: boolean; /** * How column widths are computed. Defaults to `stable`. * * - `stable` (default): the best of both. The first render with rows on screen * uses the browser's automatic layout, so each column is sized in proportion to * its real content; those measured widths are then pinned and the table switches * to a fixed layout. Columns therefore keep sensible, content-derived * proportions **and** stop moving when the rows change underneath — a new page, * a filter or a search cannot resize them. Widths are re-measured only when the * table itself is resized (or a {@link ColumnBase.hiddenBelow} column appears or * disappears), never when the rows change. Content that no longer fits is * truncated with an ellipsis and exposed as a `title` tooltip. * - `auto`: the plain browser layout. Every column is re-sized to its widest cell * on every change, so the columns shift on each new page, filter and search. * Use it for a static table, or when a cell must never be truncated. * - `fixed`: widths are **data-independent**. Nothing is measured — not the cell * content, and not the header text either. Each column is either its declared * {@link ColumnBase.width} or an even share of whatever is left over: * `(table width − Σ declared widths) ÷ number of undeclared visible columns`. * Only worth choosing over `stable` when every column declares a `width`, or * when a deliberate even split is what you want: with widths undeclared, a * two-character status column is handed exactly as much room as a long * description. */ layout?: 'auto' | 'fixed' | 'stable'; }; /** * The control rendered for a column filter, and the shape of the value it produces: * - `text` → `string` (free-text, debounced) * - `select` → `string` (single choice; empty string means "no filter") * - `multi-select` → `string[]` (OR semantics across the chosen values) * - `boolean` → `boolean` (tri-state: any / true / false) */ type ColumnFilterType = 'text' | 'select' | 'multi-select' | 'boolean'; type ColumnFilterOption = { label: string; value: string; }; /** Every value shape a column filter can hold, discriminated by {@link ColumnFilterType}. */ type ColumnFilterValue = string | string[] | boolean; /** Map of column key to its current filter value. */ type ColumnFilterState = Record; /** * One active column filter, as handed to * {@link TableDataSource.onColumnFilterChange}. Only columns whose filter is * actually set are included, so the array maps straight onto query params. */ type MnColumnFilter = { key: string; type: ColumnFilterType; value: ColumnFilterValue; }; /** * Customizes the loading-skeleton placeholder rendered in a column's cells. * Either a partial {@link MnSkeletonProps} (shape/width/height/animated) or a * `TemplateRef` for a fully custom placeholder. When omitted, a text-shaped * skeleton at 75% width is used (matching the previous default). */ type ColumnSkeleton = Partial | TemplateRef; /** * A presentation value that is either fixed for the whole column or derived per row. * * The function form is what lets one action cover a state that flips — an * activate/deactivate toggle, a pin/unpin — instead of declaring two actions and hiding * one of them per row. It is only for *presentation*: visibility stays with `hidden` and * interactivity with `disabled`, both of which are always predicates. * * Resolved on every change detection, so the accessor must be cheap and side-effect free. */ type MnRowValue = V | ((row: T) => V); /** * A per-row command rendered in an actions column (see {@link ColumnBase.actions}). * Unlike a cell it carries no display value — choosing it invokes {@link run} with the * row. The table renders actions inline as buttons and collapses them into a ⋯ menu * (mn-dropdown) once the table is narrower than 450px. */ type MnTableRowAction = { /** Visible label. Falls back to `labelKey`'s resolved text when omitted. */ label?: MnRowValue; /** Translation key for the label, resolved via MnLanguageService and kept updated on locale change. */ labelKey?: MnRowValue; /** * Optional leading icon: a template (an ``, a bespoke ``, …), or lucide * icon data such as `LucidePencil.icon`, which the table renders itself. The data form * lets an action be declared without an `` stub and a `@ViewChild` per * glyph, which is what makes shared action factories practical. */ icon?: MnRowValue; /** Invoked with the row when the action is chosen. */ run: (row: T) => void; /** * Predicate deciding whether the action is hidden for a given row. A hidden action is * dropped entirely for that row (not shown, not counted). When every action is hidden * for a row its cell is left empty — no buttons and no ⋯ menu. Use this for "row 1 has * actions, row 2 doesn't", or per-permission actions (e.g. only admins can delete). */ hidden?: (row: T) => boolean; /** Predicate deciding whether the action is disabled (shown but non-interactive) for a row. */ disabled?: (row: T) => boolean; /** * Tints the action's button and its ⋯-menu item. Defaults to `'primary'` (or * `'danger'` when {@link danger} is set), matching the built-in look. Whatever colour * an action shows inline is carried into the collapsed bottom-sheet item too. */ color?: MnRowValue; /** Renders the action in a destructive style (e.g. "Delete"). Shorthand for * `color: 'danger'`. */ danger?: boolean; }; /** Everything about a column that is independent of filtering. */ type ColumnBase = { key: string; header: string | TemplateRef; /** Translation key for the column header. When set, mn-table resolves it via MnLanguageService and keeps it updated on locale change. */ headerKey?: string; /** * How a data cell is rendered — a string accessor or a `TemplateRef`. Optional only * because an {@link actions} column renders commands instead of a value; every value * column must set it. */ cell?: ((row: T) => string) | TemplateRef; /** * Turns this column into an actions column: per-row command buttons rendered inline, * automatically collapsing into a ⋯ menu (mn-dropdown) once the table is narrower than * 450px. When set, {@link cell} is ignored. */ actions?: MnTableRowAction[]; /** * How each inline action button is presented on a wide table: * - `'both'` (default) — icon (when provided) followed by the label; * - `'icon'` — icon only; the label becomes the button's accessible name and hover * tooltip. An action without an icon falls back to showing its label so it is never * blank; * - `'label'` — text only, no icon. * * The collapsed ⋯ menu always lists full labels regardless of this setting, so * `'icon'` still reads clearly once the actions move into the bottom sheet on mobile. */ actionsInline?: 'icon' | 'label' | 'both'; /** Alternative cell renderer shown below the given breakpoint. When set, `cell` is hidden below this breakpoint and `cellSm` is shown instead. */ cellSm?: { below: 'sm' | 'md' | 'lg'; cell: ((row: T) => string) | TemplateRef; }; sortType?: ColumnSortType; getRawValueToSort?: (row: T) => unknown; width?: string; align?: 'left' | 'center' | 'right'; hiddenBelow?: 'sm' | 'md' | 'lg'; /** Customizes the loading-skeleton placeholder shown in this column's cells while data loads. */ skeleton?: ColumnSkeleton; }; /** Filter presentation props shared by every filterable column. */ type ColumnFilterCommon = { /** Whether this column supports per-column filtering. */ filterable: true; /** Placeholder text for the filter input. For `select`, it also labels the "no filter" option. */ filterPlaceholder?: string; /** Translation key for the filter placeholder. When set, mn-table resolves it via MnLanguageService. */ filterPlaceholderKey?: string; /** Whether the filter input is disabled. */ filterDisabled?: boolean; /** Autocomplete attribute for the filter input. */ filterAutocomplete?: string; }; /** * The filter half of a {@link ColumnDefinition}, discriminated on `filterType` so * `filterOptions` is required exactly where it applies and `filterFn` receives the * value shape that filter type actually produces. * * Every branch declares every filter key (inapplicable ones as `never`) so a column * can be read and written generically — e.g. mn-table resolving `filterPlaceholderKey` * across all columns — without narrowing first. */ type ColumnFilterConfig = (ColumnFilterCommon & { filterType?: 'text'; filterOptions?: never; /** Custom predicate. Receives the row and the trimmed text the user typed. */ filterFn?: (row: T, filterValue: string) => boolean; }) | (ColumnFilterCommon & { filterType: 'select'; filterOptions: ColumnFilterOption[]; /** Custom predicate. Receives the row and the selected option value. */ filterFn?: (row: T, filterValue: string) => boolean; }) | (ColumnFilterCommon & { filterType: 'multi-select'; filterOptions: ColumnFilterOption[]; /** Custom predicate. Receives the row and every selected option value. */ filterFn?: (row: T, filterValue: string[]) => boolean; }) | (ColumnFilterCommon & { filterType: 'boolean'; filterOptions?: never; /** Custom predicate. Receives the row and the chosen true/false state. */ filterFn?: (row: T, filterValue: boolean) => boolean; }) | { filterable?: false; filterType?: never; filterOptions?: never; filterFn?: never; filterPlaceholder?: string; filterPlaceholderKey?: string; filterDisabled?: never; filterAutocomplete?: never; }; type ColumnDefinition = ColumnBase & ColumnFilterConfig; type TableDataSource = MnSelectableCollectionDataSource & { /** Accessible name of the scrollable table region; without it the `mnCollection.dataTable` convention key is used. */ ariaLabel?: string; columns: ColumnDefinition[]; defaultSort?: SortState; onRowClick?: (row: T) => void; appearance?: TableAppearance; /** Template rendered on the left side of the toolbar (before the search field). */ toolbarLeftTemplate?: TemplateRef; /** Template rendered on the right side of the toolbar (after the search field). */ toolbarRightTemplate?: TemplateRef; /** * Label for the toggle button that opens the stacked filter panel on small * screens (below 640px). Defaults to "Filters". */ filtersLabel?: string; /** Translation key for {@link filtersLabel}. Resolved via MnLanguageService. */ filtersLabelKey?: string; /** * Label for the action that resets every column filter in the small-screen * panel. Defaults to "Clear all". */ clearFiltersLabel?: string; /** Translation key for {@link clearFiltersLabel}. Resolved via MnLanguageService. */ clearFiltersLabelKey?: string; /** Labels for the range / boolean filter controls. */ filterLabels?: MnTableFilterLabels; /** * Callback invoked when a column filter changes (server-side filtering). * When provided, mn-table skips client-side column filtering entirely and * delegates to the consumer, exactly as {@link MnCollectionDataSource.onServerSearch} * does for search: the table resets to page 1 and hands over every active filter. * * Required whenever filterable columns are combined with * `paginationMode: 'paginated'` — client-side filtering would otherwise only * filter the page the server already returned, while the paginator kept * reporting the unfiltered `totalItems`. * * Text filters are debounced (300ms); every other filter type fires immediately. */ onColumnFilterChange?: (filters: MnColumnFilter[]) => void; }; /** * User-facing labels for the filter controls that need more than a placeholder. * Each has a `*Key` counterpart resolved via MnLanguageService on init and on * every locale change. */ type MnTableFilterLabels = { /** Unset option of a boolean filter. Defaults to "Any". */ any?: string; anyKey?: string; /** True option of a boolean filter. Defaults to "Yes". */ yes?: string; yesKey?: string; /** False option of a boolean filter. Defaults to "No". */ no?: string; noKey?: string; /** * Summary a multi-select filter collapses to from the second selection onwards. * The `{count}` token is replaced with how many are selected. Defaults to * `{count} selected`. A column header has room for about one value, so listing * them all would overflow the cell the moment a second one is picked. */ selected?: string; /** Translation key for {@link selected}. */ selectedKey?: string; }; /** @deprecated Use {@link MnCollectionLabels}. */ type TableLabels = MnCollectionLabels; declare class MnTable extends MnSelectableCollectionBase> { /** Lucide icons the template renders. */ protected readonly icons: Record<"Funnel" | "X", _lucide_angular.LucideIconData>; sortChange: EventEmitter; rowClick: EventEmitter; currentSort: SortState | null; /** Per-column filter values keyed by column key. */ columnFilters: ColumnFilterState; /** Viewport width (px) below which the inline filter row collapses into a panel. */ private static readonly FILTER_COLLAPSE_WIDTH; /** * True when the viewport is narrow enough that the per-column filter inputs no * longer fit under their headers; the inline row is then replaced by a toggle * button and a stacked filter panel. */ protected filtersCollapsed: boolean; /** Whether the small-screen filter bottom sheet is currently open. */ protected filtersPanelOpen: boolean; /** Small-screen filter sheet, held so the close button can play its exit. */ protected filtersSheet?: MnBottomSheet; protected readonly componentName = "MnTable"; protected get trackedToolbarTemplate(): TemplateRef | undefined; protected collectionBody?: ElementRef; /** Debounces server-side text filters so typing doesn't fire a request per keystroke. */ private readonly filterDebounce; /** * Most rows shown per page on mobile (< md). A **cap**, not an override: a data * source asking for fewer rows keeps its own size. Raising a small page size on * a phone is the opposite of what it is for — it pushes the paginator below the * fold, which is most damaging inside a modal, where the sheet is already short * and its footer is pinned over the bottom of the table. */ private static readonly MOBILE_PAGE_SIZE; /** * The component's own element, measured for every responsive decision. Typed via * the annotation, not `inject(ElementRef)` — that form is a generic * call on the token and leaves `nativeElement` untyped. */ private readonly host; /** Whether the consumer owns filtering (server-side), mirroring {@link isServerSearched}. */ get isServerFiltered(): boolean; /** Every column filter that is actually set, in column order. */ get activeColumnFilters(): MnColumnFilter[]; /** Whether at least one column filter is active. */ get hasActiveFilters(): boolean; /** * Updates a column filter value and either re-filters locally or hands the * active filters to the consumer. Server-side text filters are debounced; * every other type commits immediately. */ onColumnFilter(column: ColumnDefinition, value: ColumnFilterValue): void; /** Updates a tri-state boolean filter from its select ('' = any). */ onBooleanFilter(column: ColumnDefinition, raw: string): void; /** The effective filter type of a column, defaulting to text. */ filterTypeOf(column: ColumnDefinition): ColumnFilterType; /** Whether a specific column's filter currently narrows the rows. */ isColumnFilterActive(column: ColumnDefinition): boolean; /** Filter options formatted for mn-multi-select for a given column. */ getFilterMultiSelectOptions(column: ColumnDefinition): MnMultiSelectOption[]; /** Label for the small-screen filters toggle button. */ get filtersButtonLabel(): string; /** * Summary a multi-select filter collapses to once more than one option is picked. * Resolved with the `{count}` token intact for mn-multi-select to fill in. */ get filterSelectedLabel(): string; /** Current text/select filter value for a column. */ textFilterValue(column: ColumnDefinition): string; /** Current multi-select filter value for a column. */ multiFilterValue(column: ColumnDefinition): string[]; /** Current boolean filter value for a column, as the select's string value. */ booleanFilterValue(column: ColumnDefinition): string; /** Resets every column filter and re-applies (or re-requests) filtering. */ clearAllFilters(): void; /** Whether any column has filtering enabled. */ get hasColumnFilters(): boolean; /** Label for the "clear all filters" action in the small-screen panel. */ get clearFiltersButtonLabel(): string; /** Accessible label for the filter sheet's close button. */ get filtersCloseLabel(): string; /** Heading for the selection summary, with the count filled in. */ get selectionSummaryTitle(): string; /** Label for the summary's clear-everything action. */ get selectionClearAllLabel(): string; /** Opens the small-screen filter bottom sheet. */ openFiltersPanel(): void; /** Plays the sheet's slide-down exit, then unmounts it. */ closeFiltersPanel(): Promise; private readonly baseTableClasses; /** * Column widths measured from the automatic layout and pinned, keyed by column * key, for `stable`. Empty until the first render that has real rows on screen, * and cleared whenever the table is resized so the next render re-measures. */ private pinnedWidths; /** Sets sort/filter state seeded from the data source before the first filter pass. */ protected beforeInitialFilter(): void; /** * Whether {@link pinColumnWidths} has run. Tracked separately from * {@link pinnedWidths} being non-empty, because the widest column is deliberately * left unpinned and a table with a single flexible column therefore pins nothing. */ private widthsPinned; /** * Recomputes whether the inline filter row should collapse into the panel. * Closes the panel when returning to the wide layout so reopened state never * leaks across the breakpoint. Marks for check only when the layout flips. */ private updateFilterLayout; sort(column: ColumnDefinition): void; onRowClick(row: T): void; /** * Resolves the skeleton placeholder config for a column's cells. * Falls back to a text-shaped bar at 75% width (the previous default); any * fields the column provides override that default. */ getColumnSkeletonData(column: ColumnDefinition): Partial; getSortIcon(column: ColumnDefinition): string; /** * Accessible name for a column's inline filter control: the header text, or the column key when * the header is a template. * @param column - The filtered column. */ filterLabel(column: ColumnDefinition): string; /** * A string column's header text. `headerKey` is translated here, at render time, rather than * only in `resolveTranslationKeys`: that runs on init and on a locale change, so a column a * consumer adds afterwards (a permission-gated actions or image column) kept an empty header, * which a screen reader announces as a nameless column. * @param column - The column whose header is shown. * @returns The translated key when the column has one, otherwise its literal header, or an * empty string for a template header (rendered through its own outlet instead). */ headerText(column: ColumnDefinition): string; isSortable(column: ColumnDefinition): boolean; constructor(); /** * Classes for the `` element. `table-fixed` is added once column widths * are no longer allowed to follow the content: always for the `fixed` layout, and * for `stable` from the moment its widths have been measured and pinned. */ get tableClasses(): string; /** Page size to use at/above the `md` breakpoint (consumer's pageSize, or the user's selection). */ private desktopPageSize; /** Label for the summary's expand/collapse control. */ get selectionSummaryToggleLabel(): string; /** Placeholder and accessible name for the search box. */ get searchPlaceholderLabel(): string; /** Screen-reader-only header text of the selection column, so that column is never nameless. */ get selectionColumnLabel(): string; /** Accessible name for the scrollable table region. */ get tableRegionLabel(): string; /** * Fewer tags once the table is narrow. A tag holding a person's full name takes * a whole line at phone width, so the eight that read as a compact header on a * wide table become eight stacked lines in a modal sheet — the summary then * occupies more of the screen than the rows it is summarising. * * Reuses {@link filtersCollapsed} rather than measuring again: it is already * maintained on every resize and means exactly "this table is under 640px". * The heading still states the true total, so the hidden tags cost no information. */ protected get defaultSelectionSummaryLimit(): number; /** Tracks the desktop page size when the user picks one (selector only shows at >= md). */ onPageSizeChange(newSize: number): void; /** The effective column-width strategy, defaulting to `stable`. */ get layoutMode(): 'auto' | 'fixed' | 'stable'; /** * Whether column widths have stopped following the cell content — `fixed` always, * `stable` once {@link pinColumnWidths} has captured them. Drives `table-fixed` * and the cell truncation together, so a cell is never clipped while the column * it sits in could still have grown to fit it. */ get widthsArePinned(): boolean; /** Any / Yes / No options for a boolean column filter. */ getBooleanFilterOptions(column: ColumnDefinition): MnSelectOption[]; /** * The width to render for a column: the consumer's own declared width always * wins, then a width pinned by the `stable` layout, otherwise none. * @param column The column being rendered. * @returns A CSS width, or `null` to leave it to the layout algorithm. */ columnWidth(column: ColumnDefinition): string | null; /** * The `title` tooltip for a cell, so text truncated by a pinned column stays * readable. Only string cells have text to expose; template cells render their * own markup and are left alone. * @param column The column being rendered. * @param row The row being rendered. * @returns The full cell text, or `null` when there is nothing to expose. */ cellTitle(column: ColumnDefinition, row: T): string | null; /** * Falls back to the first column that renders a plain string, which is almost * always the name-like column a person would use to identify the row. Template * columns are skipped: they render markup this cannot flatten to a tag label. * @param row The selected row. * @returns The label, or null when every column renders a template. */ protected defaultSelectionLabel(row: T): string | null; /** * Resolves table-specific translation keys (column headers/filters) plus the * shared keys handled by the base. */ protected resolveTranslationKeys(): void; protected applyFilter(searchForItems: boolean): void; /** * Re-evaluate on a window resize too. The ResizeObserver covers every change to * the table's own box, but {@link isMobileViewport} reads the window, which can * change without the table's width following it (a fixed-width table, a modal * pinned to a max width). */ protected onWindowResize(): void; /** Re-evaluate responsive page size and filter layout when the table is resized. */ private onHostResize; /** * Captures the current, automatically-derived width of every visible column and * pins it, which flips the table to `table-fixed` on the next render. * * Runs only with real rows on screen: measuring the loading skeletons would pin * the placeholder bars' widths rather than the data's. Hidden columns * ({@link ColumnBase.hiddenBelow}) measure 0 and are skipped, so they are free to * size themselves if a resize later reveals them. * * The **widest** column is measured but deliberately left unpinned, so it absorbs * whatever space the pinned ones leave over. Pinning every column instead makes the * widths sum to slightly more than the container — `border-collapse` shares borders * between neighbours, so rounding each cell's measured width over-counts them — and * the table then overflows into a spurious horizontal scrollbar. Leaving one column * elastic also means a later resize squeezes the widest column first instead of * clipping every column equally. */ private pinColumnWidths; /** * Drops the pinned widths so the next render with rows re-measures them. Called * when the table is resized: the old pixel widths were shares of a box that no * longer exists, and a resize is also what makes `hiddenBelow` columns come and * go, changing which columns need a share at all. */ private unpinColumnWidths; getCellValue(column: ColumnDefinition, row: T): string; /** Returns the small-screen cell value for a column with cellSm defined. */ getCellSmValue(column: ColumnDefinition, row: T): string; /** The actions visible for a given row — those whose `hidden(row)` is not true. */ visibleRowActions(column: ColumnDefinition, row: T): MnTableRowAction[]; /** Whether a row has any visible actions at all; when false its cell is left empty. */ hasRowActions(column: ColumnDefinition, row: T): boolean; /** * Whether a row's actions fold into the ⋯ menu below 450px. They do unless the row has * exactly one visible action that renders as a bare icon: that button is narrower than the * ⋯ trigger it would hide behind, so collapsing it only puts a second tap in front of the * one command the row has. */ collapsesRowActions(column: ColumnDefinition, row: T): boolean; /** * Resolves a {@link MnRowValue}: either the fixed value, or the accessor applied to * the row. Every per-row presentation field goes through here so the fixed and derived * forms can never drift apart. */ private resolveRowValue; /** The resolved label for an inline action button (translation key wins once resolved). */ rowActionLabel(action: MnTableRowAction, row: T): string; /** The resolved leading icon for an action on a given row, if it has one. */ rowActionIcon(action: MnTableRowAction, row: T): MnActionIcon | undefined; /** Whether an inline action button should render its icon. */ showActionIcon(column: ColumnDefinition, action: MnTableRowAction, row: T): boolean; /** * Whether an inline action button should render its text label. In `'icon'` mode the * label is hidden — unless the action has no icon, in which case it is shown anyway so * the button is never blank. */ showActionLabel(column: ColumnDefinition, action: MnTableRowAction, row: T): boolean; /** Whether an action is disabled for the given row. */ isRowActionDisabled(action: MnTableRowAction, row: T): boolean; /** * The effective colour for an action, used identically by the inline button and the * collapsed ⋯-menu item so the two never diverge: an explicit `color`, else `'danger'` * for a destructive action, else the default `'primary'`. */ rowActionColor(action: MnTableRowAction, row: T): MnDropdownActionColor; /** Invokes an action for a row. */ runRowAction(action: MnTableRowAction, row: T): void; /** A stable, unique element id for a row's actions dropdown (aria wiring). */ actionsDropdownId(column: ColumnDefinition, row: T): string; /** Maps a row's visible actions to mn-dropdown commands, binding the row into each. */ rowDropdownActions(column: ColumnDefinition, row: T): MnDropdownAction[]; trackByKey: (_index: number, column: ColumnDefinition) => string; /** True when the table is narrower than the filter-collapse breakpoint. */ private isFilterViewport; /** * True when the **window** is below the `md` (768px) breakpoint. * * Deliberately viewport-based, unlike {@link isFilterViewport}: the forced * mobile page size exists to keep a phone screen scrollable, and it is paired * with the rows-per-page selector that mn-collection-pagination hides at the * same viewport breakpoint. Measuring the table's own width instead would let * the two disagree — a 700px table on a desktop would be pinned to the mobile * row count while still offering the selector that overrides it. */ private isMobileViewport; /** * The table's own rendered width, which every responsive decision is made * against — the same width the `@container` queries in the template use, so * the TS and CSS halves of the responsive layout can never disagree. * * Falls back to the window width before the host has been laid out (and in * SSR), which is the closest available approximation at that point. * @returns The width in CSS pixels. */ private measuredWidth; /** * Applies the breakpoint-appropriate page size: capped at {@link MOBILE_PAGE_SIZE} * below `md`, the desktop size at/above it. When the size actually changes, client-side tables * re-slice locally and server-side tables ask the consumer to refetch, so the * rendered rows update in every pagination mode (used at init and on window resize). */ private applyResponsivePageSize; get totalColumnCount(): number; /** * Hands the active filters to the consumer. Locks the body height first so the * skeleton swap during the refetch can't collapse the layout, matching * {@link goToPage} and {@link onSearch}. */ private emitServerFilters; /** Resets every filterable column to its type's empty value. */ private seedFilterValues; /** * Resolves the selection-summary labels from their translation keys. Resolved * without params so the `{{count}}` / `{{label}}` placeholders survive for the * getters to fill in per render. */ private resolveSelectionSummaryKeys; /** Resolves the range / boolean filter control labels from their translation keys. */ private resolveFilterLabelKeys; private applySorting; /** Filter options formatted for mn-select for a given column. */ getFilterSelectOptions(column: ColumnDefinition): MnSelectOption[]; /** * Accessible label for a tag's remove button. * @param row The row the tag stands for. * @returns The label, naming the row so screen readers announce which one goes. */ selectionRemoveLabel(row: T): string; static ɵfac: i0.ɵɵFactoryDeclaration, never>; static ɵcmp: i0.ɵɵComponentDeclaration, "mn-table", never, {}, { "sortChange": "sortChange"; "rowClick": "rowClick"; }, never, never, true, never>; } /** * Pure helpers backing mn-table's per-column filters: the empty value and * "is it set?" test for each filter type, and the default client-side predicate * applied when a column supplies no `filterFn`. * * Kept free of Angular so the filter semantics can be unit-tested directly. */ /** The reset/unset value for a filter type. */ declare function emptyFilterValue(type: ColumnFilterType): ColumnFilterValue; /** * Whether a filter value should actually narrow the rows. Empty strings and * empty arrays are inactive; `false` on a boolean filter is active (it means * "show only the false rows"), which is why a plain truthiness check is not * enough. */ declare function isFilterValueActive(value: ColumnFilterValue | undefined): boolean; /** * The value a filter compares against for a row: the column's * `getRawValueToSort` when present (the only option for template cells, which * have no string to read), otherwise the rendered cell string. */ declare function resolveFilterableValue(column: ColumnDefinition, row: T): unknown; /** * The default predicate for a filter type, used when the column supplies no * `filterFn`. Semantics per type: * - `text` — case-insensitive substring match * - `select` — exact string equality * - `multi-select` — equality against any selected value (OR) * - `boolean` — truthiness of the raw value equals the chosen state */ declare function defaultFilterPredicate(type: ColumnFilterType, raw: unknown, value: ColumnFilterValue): boolean; /** * Whether a row passes a column's active filter — the column's own `filterFn` * when it has one, otherwise {@link defaultFilterPredicate}. * * `filterFn` is declared per filter type on {@link ColumnDefinition}, so at this * generic call site the union of signatures is not callable and the value shape * is widened once here. Consumers keep the precise per-type signature where they * declare the column, which is where it matters. */ declare function matchesColumnFilter(column: ColumnDefinition, row: T, value: ColumnFilterValue): boolean; /** * Attribute directive that applies responsive-hiding classes to table cells/headers. * Hides the element by default and shows it as `table-cell` at the specified breakpoint. * * The breakpoints are **container** queries against the table's own width, not the * viewport: a table inside a modal (or any narrow column) is far narrower than the * window, so viewport breakpoints would reveal columns the table has no room for. * mn-table marks its chrome `@container` for exactly this. * * Because of that, `sm`/`md`/`lg` mean "the table is at least this wide", and the * thresholds are **not** the viewport values of the same names — see {@link classMap}. * * Uses a static class map so Tailwind CSS can detect the full class names at build time. * * Usage: `
` */ declare class MnHiddenBelowDirective implements OnChanges { /** The breakpoint below which the element is hidden. */ mnHiddenBelow: 'sm' | 'md' | 'lg' | undefined; private readonly el; private readonly renderer; private appliedClasses; /** * Static mapping of breakpoints to their full Tailwind class names, so Tailwind * can detect them at build time. * * These are **container** widths, deliberately lower than the viewport * breakpoints they are named after. A table almost never gets the whole window: * a page table sits inside a docked sidebar plus page padding, which on a * 1280px screen leaves it under 900px. Reusing 1024px for `lg` would demand a * ~1400px window before an `lg` column ever appeared — hiding columns on the * most ordinary laptop. These values instead express how much room the column * itself needs, which is what a container query should measure. */ private readonly classMap; ngOnChanges(): void; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵdir: i0.ɵɵDirectiveDeclaration; } /** * Attribute directive that hides an element below the given breakpoint and shows it at/above. * Uses `hidden` + `{bp}:inline` so the element is invisible on small screens. * * Usage: `` */ declare class MnShowAboveDirective implements OnChanges { /** The breakpoint at/above which the element becomes visible. */ mnShowAbove: 'sm' | 'md' | 'lg' | undefined; private readonly el; private readonly renderer; private appliedClasses; /** Static mapping of breakpoints to their full Tailwind class names. */ private readonly classMap; ngOnChanges(): void; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵdir: i0.ɵɵDirectiveDeclaration; } /** * Attribute directive that shows an element below the given breakpoint and hides it at/above. * Uses `inline` by default + `{bp}:hidden` so the element is only visible on small screens. * * Usage: `` */ declare class MnShowBelowDirective implements OnChanges { /** The breakpoint below which the element is visible. */ mnShowBelow: 'sm' | 'md' | 'lg' | undefined; private readonly el; private readonly renderer; private appliedClasses; /** Static mapping of breakpoints to their full Tailwind class names. */ private readonly classMap; ngOnChanges(): void; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵdir: i0.ɵɵDirectiveDeclaration; } /** * Customizes the loading-skeleton placeholder rendered for each list item. * Either a set of skeleton lines (each a partial {@link MnSkeletonProps}) stacked * vertically, or a `TemplateRef` for a fully custom placeholder. When omitted, * two text-shaped lines (75% and 50% width) are used, matching the previous default. */ type ListSkeleton = { lines: Partial[]; } | TemplateRef; type ListAppearance = { /** Show a divider between items. Defaults to true. */ dividers?: boolean; /** Highlight item on hover. Defaults to true. */ hover?: boolean; /** Use compact (smaller) padding. */ compact?: boolean; /** Show a border around the list. */ bordered?: boolean; }; type ListDataSource = MnSelectableCollectionDataSource & { /** Template used to render each list item. Receives the item as `$implicit` and `data`. */ itemTemplate: TemplateRef; /** Customizes the loading-skeleton placeholder shown for each item while data loads. */ skeleton?: ListSkeleton; onItemClick?: (item: T) => void; appearance?: ListAppearance; /** Template rendered on the left of the toolbar, before the search field. */ toolbarLeftTemplate?: TemplateRef; /** Template rendered on the right of the toolbar, after the search field. */ toolbarRightTemplate?: TemplateRef; /** * @deprecated Use {@link toolbarRightTemplate}, which names the slot it fills. * Still honoured, and still rendered on the right, so existing callers keep * working unchanged. */ toolbarTemplate?: TemplateRef; }; /** @deprecated Use {@link MnCollectionLabels}. */ type ListLabels = MnCollectionLabels; declare class MnList extends MnSelectableCollectionBase> { itemClick: EventEmitter; protected readonly componentName = "MnList"; /** Skeleton lines rendered for each placeholder item, falling back to the default two-bar layout. */ get skeletonLines(): Partial[]; onItemClick(item: T): void; /** * Keyboard activation of a clickable item: Enter and Space open it, as they would a button. * Handled on keydown so Space does not scroll the page first, and only when the item itself has * focus, so a checkbox or button inside the item keeps its own keys. * @param event - The keydown on the item. * @param item - The item the key was pressed on. */ onItemKeydown(event: KeyboardEvent, item: T): void; /** * The toolbar template the base class watches for identity changes. Prefers the * left slot, then the right, then the deprecated `toolbarTemplate`, so a list * using any single slot still re-renders when that template is swapped. */ protected get trackedToolbarTemplate(): TemplateRef | undefined; protected collectionBody?: ElementRef; protected applyFilter(searchForItems: boolean): void; /** Accessible name for the scrollable list region. */ get listRegionLabel(): string; /** Label on the header checkbox that selects or clears every visible row. */ get selectAllLabel(): string; static ɵfac: i0.ɵɵFactoryDeclaration, never>; static ɵcmp: i0.ɵɵComponentDeclaration, "mn-list", never, {}, { "itemClick": "itemClick"; }, never, never, true, never>; } /** * Controls the responsive card layout. Provide **either** `cols` (explicit column * counts per breakpoint) **or** `minCardWidth` (CSS `auto-fit`/`minmax`); when * `minCardWidth` is set it takes precedence and `cols` is ignored. * * Breakpoints match Tailwind defaults: sm 640px, md 768px, lg 1024px, xl 1280px. */ type GridLayout = { /** * Explicit column count per breakpoint. Each falls back to the next-smaller one. * Counts are clamped to 1–12, the range Tailwind's `grid-cols-*` utilities cover. */ cols?: { base?: number; sm?: number; md?: number; lg?: number; xl?: number; }; /** Minimum card width, e.g. '18rem'. Enables auto-fit layout; ignores `cols`. */ minCardWidth?: string; /** Gap between cards. Defaults to '1rem'. */ gap?: string; /** * Caps the number of cards shown (preview mode, e.g. "first 3"). Intended for * `paginationMode: 'none'`; it slices the visible set and the pager stays hidden. */ maxItems?: number; }; /** * Customizes the loading-skeleton placeholder rendered for each card while data * loads. Provide a `TemplateRef` to profile the card's shape (the primary, most * useful form), or a set of stacked skeleton lines. When omitted, a default card * placeholder (image block + two text bars) is rendered. */ type GridSkeleton = TemplateRef | { lines: Partial[]; }; /** * Configures an {@link import('./mn-grid.component').MnGrid}. * * Empty state (inherited from {@link MnCollectionDataSource}): provide **either** * a default text via `emptyMessage` / `emptyMessageKey`, **or** a whole custom * component/markup via `emptyTemplate`. A supplied `emptyTemplate` is rendered * unwrapped — it fully controls its own layout — while the text variant gets the * grid's default centered placeholder. */ type GridDataSource = MnCollectionDataSource & { /** Template used to render each card. Receives the item as `$implicit` and `data`. */ cardTemplate: TemplateRef; /** Customizes the loading-skeleton card shown while data loads. */ skeleton?: GridSkeleton; /** Responsive layout configuration. */ layout?: GridLayout; onItemClick?: (item: T) => void; /** Template rendered on the left of the toolbar, before the search field. */ toolbarLeftTemplate?: TemplateRef; /** Template rendered on the right of the toolbar, after the search field. */ toolbarRightTemplate?: TemplateRef; /** * @deprecated Use {@link toolbarRightTemplate}, which names the slot it fills. * Still honoured, and still rendered on the right, so existing callers keep * working unchanged. */ toolbarTemplate?: TemplateRef; }; /** * Responsive card-grid component. Shares the collection chrome (search, every * pagination mode, loading skeleton, empty state, toolbar, i18n) with * {@link import('../mn-list').MnList} and {@link import('../mn-table').MnTable} * via {@link MnCollectionBase}, and lays items out as cards instead of rows. * Selection is intentionally not supported. */ declare class MnGrid extends MnCollectionBase> { itemClick: EventEmitter; protected readonly componentName = "MnGrid"; /** Whether the grid uses auto-fit (minCardWidth) instead of explicit columns. */ get isAutoLayout(): boolean; /** * Classes for the card container: `grid` plus one column utility per * breakpoint the consumer configured. Omitted for the auto-fit layout, whose * columns come from {@link autoTemplateColumns} instead. */ get gridClasses(): string; /** Gap between cards. */ get gridGap(): string; /** * Inline `grid-template-columns` for the auto-fit layout, or null when explicit * `cols` are used (the utilities in {@link gridClasses} then own the columns). * `minCardWidth` is a free-form CSS length, so it can only be expressed inline. */ get autoTemplateColumns(): string | null; /** Skeleton lines for the default/lines placeholder; null when a custom template is used. */ get skeletonLines(): Partial[]; /** * The toolbar template the base class watches for identity changes. Prefers the * left slot, then the right, then the deprecated `toolbarTemplate`, so a grid * using any single slot still re-renders when that template is swapped. */ protected get trackedToolbarTemplate(): TemplateRef | undefined; protected collectionBody?: ElementRef; onItemClick(item: T): void; /** * Keyboard activation of a clickable item: Enter and Space open it, as they would a button. * Handled on keydown so Space does not scroll the page first, and only when the item itself has * focus, so a checkbox or button inside the item keeps its own keys. * @param event - The keydown on the item. * @param item - The item the key was pressed on. */ onItemKeydown(event: KeyboardEvent, item: T): void; protected applyFilter(searchForItems: boolean): void; /** Accessible name for the scrollable grid region. */ get gridRegionLabel(): string; static ɵfac: i0.ɵɵFactoryDeclaration, never>; static ɵcmp: i0.ɵɵComponentDeclaration, "mn-grid", never, {}, { "itemClick": "itemClick"; }, never, never, true, never>; } export { ColumnSortType, MnCollectionBase, MnCollectionPagination, MnCollectionState, MnGrid, MnHiddenBelowDirective, MnList, MnSelectableCollectionBase, MnShowAboveDirective, MnShowBelowDirective, MnTable, defaultFilterPredicate, emptyFilterValue, isFilterValueActive, matchesColumnFilter, resolveFilterableValue }; export type { ColumnBase, ColumnDefinition, ColumnFilterOption, ColumnFilterState, ColumnFilterType, ColumnFilterValue, ColumnSkeleton, CursorPaginationStrategy, GridDataSource, GridLayout, GridSkeleton, ListAppearance, ListDataSource, ListLabels, ListSkeleton, MnCollectionDataSource, MnCollectionLabels, MnColumnFilter, MnPageSlot, MnRowValue, MnSelectableCollectionDataSource, MnTableFilterLabels, MnTableRowAction, OffsetPaginationStrategy, PaginationMode, PaginationStrategy, SortState, TableAppearance, TableDataSource, TableLabels };