import { ChangeDetectorRef, EventEmitter, OnDestroy } from '@angular/core'; import { ConnectedPosition } from '@angular/cdk/overlay'; import { FolderNode, FolderRenamedPayload, ModularLabels } from '../modular-table-template.models'; import * as i0 from "@angular/core"; /** A row in the flattened sidebar tree. `kind` distinguishes real folder rows * from the lazy-load affordances ('loading' spinner, 'load-more' button) that * the `rows` getter interleaves inside an expanded subtree. */ interface RenderNode { kind: 'folder' | 'loading' | 'load-more'; node: FolderNode; depth: number; visible: boolean; hasChildren: boolean; } export declare class FolderSidebarComponent implements OnDestroy { private cdr; folders: FolderNode[]; selectedFolderId: number | null; expandedFolderIds: number[]; unorganizedCount: number; allowCreate: boolean; allowRename: boolean; allowDelete: boolean; allowMove: boolean; allowDuplicate: boolean; allowDrop: boolean; showCounts: boolean; collapsed: boolean; foldersAccordionExpanded: boolean; labels: ModularLabels; /** When true, the sidebar emits `searchChange` and skips its local name filter * — the host is expected to swap `folders` with server-side search results. * Default keeps the legacy client-side behaviour for callers that haven't * opted into lazy loading yet. */ serverSideSearch: boolean; /** Backend-reported total root folders. When `folders.length < rootTotal` the * sidebar renders a top-level "Load more" affordance after the tree. */ rootTotal: number; /** True while a server-side folder-search request is in flight. The sidebar * shows a spinner next to the search input so the user can tell typing * has been registered. */ folderSearchLoading: boolean; /** True while a root-page fetch is in flight (initial load OR scroll-driven * pagination). When set with no folders yet, the sidebar shows a centered * spinner; when set during pagination, a small spinner appears at the * bottom of the tree. */ rootFoldersLoading: boolean; /** Folder ids currently being saved (rename / move / etc). The sidebar renders * a small spinner in place of the count so the user gets feedback while the * host's API call is in flight. The host is responsible for adding/removing * ids around its async call. */ savingFolderIds: Array; isFolderSaving(id: number): boolean; folderRowTooltip(node: FolderNode): string | null; /** Visual depth indent: linear up to a cap, then quarter-rate beyond, so deep * trees don't push the row content off the right edge of the panel. */ rowIndent(depth: number): number; folderSelected: EventEmitter; folderExpansionToggled: EventEmitter<{ id: number; expanded: boolean; }>; /** Lazy-load: fires when the user expands a node whose children haven't been * fetched yet (`hasChildren === true && loadedPages === undefined`). Host * responds by calling its `loadFolderChildren(id, 0)`. */ folderChildrenRequested: EventEmitter; /** Lazy-load: fires when the user clicks the per-folder "Load more" row. * Host responds by calling `loadFolderChildren(id, nextPage)`. */ folderLoadMoreRequested: EventEmitter<{ id: number; nextPage: number; }>; /** Debounced search-input value. Always emitted; only consumed by hosts that * set `serverSideSearch=true` to re-fetch the tree from the backend. */ searchChange: EventEmitter; /** Lazy-load: user clicked the top-level "Load more" affordance for roots. */ rootLoadMoreRequested: EventEmitter; /** Emitted after the host completes folder creation (e.g., inline rename flow). Reserved for future use. */ folderCreated: EventEmitter<{ parentId: number | null; name: string; }>; /** Emitted when the user clicks the "+" header button. Host should open a creation modal. */ folderCreateRequested: EventEmitter<{ parentId: number | null; }>; /** Carries the full renamed folder node + parent id so hosts can issue complete update requests. */ folderRenamed: EventEmitter; folderDeleted: EventEmitter<{ id: number; }>; /** User picked "Move folder" from the context menu. Host opens the folder picker. */ folderMoveRequested: EventEmitter<{ id: number; }>; /** User picked "Duplicate folder" from the context menu. Host clones the subtree (tests referenced, not copied). */ folderDuplicateRequested: EventEmitter<{ id: number; }>; testsDropped: EventEmitter<{ testIds: Array; targetFolderId: number | null; }>; /** Fires when a folder row is dropped onto another folder (or the Unorganized row). */ folderDropped: EventEmitter<{ id: number; newParentId: number | null; }>; collapsedChange: EventEmitter; foldersAccordionExpandedChange: EventEmitter; /** Bound to the search input. Exposed as an Input so hosts can seed/restore * the visible search term across destroy/re-create cycles (e.g., when the * sidebar is `*ngIf`'d out by a view-mode toggle and re-mounted later). */ searchValue: string; /** Debounce timer for `searchChange` emissions; cleared on each keystroke so * the host only re-fetches once the user pauses typing. */ private searchEmitTimer; private static readonly SEARCH_DEBOUNCE_MS; renamingId: number | null; renameDraft: string; /** Inline rename uses the same name rules as the New Folder dialog. */ readonly renameMaxLength: number; /** Transient flag: turns the rename input red briefly when the user keystrokes * while already at `renameMaxLength`, so the silent maxlength clamp gets a * visible "you've hit the cap" signal. Cleared by `renameLimitFlashTimer`. */ renameLimitFlash: boolean; private renameLimitFlashTimer; private static readonly RENAME_LIMIT_FLASH_MS; /** Id of the folder whose context menu is open, or null when closed. */ contextMenuFolderId: number | null; /** Suppress outside-click handling until this timestamp; protects against the * right-click that opens the menu also being interpreted as an outside click. */ private _suppressOutsideClickUntil; /** Viewport-anchored position (client coordinates) for the floating menu. * Drives the invisible cdkOverlayOrigin anchor; CDK Overlay then picks the * best of `contextMenuPositions` so the menu never overflows the viewport. */ contextMenuPosition: { x: number; y: number; }; /** Position priority for the CDK-overlay-rendered context menu. CDK tries each * in order and uses the first one that fits — so the menu opens downward by * default and flips up / left when too close to the bottom or right edge. */ readonly contextMenuPositions: ConnectedPosition[]; /** Pending auto-expand timers while a folder drag is hovered over collapsed rows. */ private dragExpandTimers; private readonly DRAG_EXPAND_DELAY_MS; /** Id of the folder currently being dragged; used to skip cycle targets and source-hover expansion. */ private activeFolderDragId; constructor(cdr: ChangeDetectorRef); get expandedSet(): Set; /** Flattened, depth-aware list of rows to render in order. Ancestors of matches are kept. */ get rows(): RenderNode[]; isExpanded(id: number): boolean; isSelected(id: number | null): boolean; onToggle(n: FolderNode, event: Event): void; /** Search input handler: updates the local `searchValue` immediately (so the * input stays responsive) and debounces `searchChange` emissions so a host * doing server-side search fires at most one request per pause. */ onSearchInput(term: string): void; /** Search input clear handler: flushes the debounce so the host sees an * empty term immediately and can restore the lazy tree. */ onSearchClear(): void; /** Lazy-load: user clicked the per-folder "Load more" row. The sidebar tracks * pagination via `node.loadedPages`; the next page is loadedPages + 1. */ onLoadMore(n: FolderNode): void; /** Cached `folders.length` at the moment we last emitted `rootLoadMoreRequested`. * Used to throttle the scroll-based pagination so we don't re-emit until the * host has actually appended a new page. */ private lastRootLoadEmittedAt; /** Threshold (px) for "near the bottom" — emit a load-more once the scroll * position is within this many px of the end. */ private static readonly SCROLL_LOAD_THRESHOLD_PX; /** Vertical-scroll handler for the root-folder list. Fires `rootLoadMoreRequested` * when the user scrolls within the threshold of the bottom and more roots * are available on the backend. Suppressed during sidebar search. */ onTreeScroll(event: Event): void; onSelect(n: FolderNode | null): void; onSelectUnorganized(): void; beginRename(n: FolderNode, event: Event): void; /** True when the active rename draft passes the name rules (non-empty, within max length). */ get isRenameDraftValid(): boolean; commitRename(n: FolderNode): void; cancelRename(): void; onRenameKey(event: KeyboardEvent, n: FolderNode): void; private isPrintableKey; private wouldExceedRenameLimit; private triggerRenameLimitFlash; private clearRenameLimitFlash; deleteFolder(n: FolderNode, event: Event): void; /** * Opens the folder context menu anchored at the given client coordinates. * Called from both (contextmenu) on the row and (click) on the ellipsis button. * The template renders the menu via cdkConnectedOverlay anchored on an * invisible 0×0 origin at `contextMenuPosition`; CDK's flexible-connected * positioning auto-flips up/left when the preferred placement would overflow * the viewport (e.g. right-clicking the last folder near the bottom edge). */ openContextMenu(n: FolderNode, event: MouseEvent): void; onOverlayOutsideClick(): void; closeContextMenu(): void; /** True when at least one menu entry is enabled — otherwise the trigger is suppressed entirely. */ get hasAnyContextAction(): boolean; onContextCreateSubfolder(n: FolderNode): void; onContextRename(n: FolderNode): void; onContextMove(n: FolderNode): void; onContextDuplicate(n: FolderNode): void; onContextDelete(n: FolderNode): void; onDocumentClick(): void; onEscape(): void; /** Template helper: resolve a folder node by id from the input tree. */ folderById(id: number | null): FolderNode | null; /** Returns the parent id of `id`, or null when `id` is a root folder / not found. */ private parentIdOf; requestCreate(parentId?: number | null): void; togglePanel(): void; toggleFoldersAccordion(): void; /** Toggles the inline folder-search input. When closing while a query is active, * also clear the search so the tree reverts to its full state. */ searchVisible: boolean; toggleSearchVisible(): void; rowDropped(targetId: number | null, event: { testIds: Array; targetFolderId: number | null; }): void; /** * Drop target received a folder drag. Validate against self/descendant cycles and * emit `folderDropped` if the move is legal. Called from the directive's * `(folderDropped)` output on every row and on the Unorganized row. */ onFolderRowDropped(targetId: number | null, event: { folderId: number; targetFolderId: number | null; }): void; /** Remember the dragged folder so we can block self-hover expansion and cycle drops. */ onFolderDragStart(n: FolderNode): void; onFolderDragEnd(): void; /** Schedule an auto-expand for a collapsed folder that has children during drag-hover. */ onFolderRowDragOver(row: RenderNode): void; onFolderRowDragLeave(row: RenderNode): void; onDocumentDragEnd(): void; /** True when `candidateId` is `sourceId` itself or any descendant of it. */ private isDescendantOf; private clearAllExpandTimers; ngOnDestroy(): void; trackByRow: (_: number, row: RenderNode) => string; onRowKeydown(event: KeyboardEvent, row: RenderNode, index: number): void; private focusRowElement; /** * Focuses the inline rename input for the given folder. Uses `setTimeout` (not * microtask) because the input is gated by `*ngIf="renamingId === ..."` and * must survive the next OnPush change-detection cycle before it's in the DOM. */ private focusRenameInput; static ɵfac: i0.ɵɵFactoryDeclaration; static ɵcmp: i0.ɵɵComponentDeclaration; } export {};