UNPKG

ng-hub-ui-board

Version:

An Angular-based Kanban board component with Trello-like drag-and-drop, customizable columns, and straightforward event handling.

1,050 lines 90.8 kB
import * as i0 from '@angular/core';
import { Directive, input, computed, signal, contentChild, TemplateRef, viewChild, output, Component, NgModule, Pipe } from '@angular/core';
import { NgClass, NgTemplateOutlet } from '@angular/common';
import { moveItemInArray, transferArrayItem, createNativeDragImage } from 'ng-hub-ui-utils';
export { moveItemInArray, transferArrayItem } from 'ng-hub-ui-utils';

/**
 * Directive that allows customization of column footer templates within board columns.
 *
 * This directive enables developers to define custom templates for column footers,
 * perfect for displaying summary information, quick actions, statistics,
 * or any column-specific controls at the bottom of each column.
 *
 * @publicApi
 *
 * @example
 * ```html
 * <ng-template columnFooterTpt let-column="column">
 *   <div class="custom-footer">
 *     <div class="column-summary">
 *       <span>Total: {{ column.cards.length }}</span>
 *       <span>Priority Items: {{ getPriorityItems(column) }}</span>
 *     </div>
 *     <button (click)="quickAddCard(column)">Quick Add</button>
 *   </div>
 * </ng-template>
 * ```
 */
class BoardColumnFooterDirective {
    templateRef;
    /**
     * Creates a new BoardColumnFooterDirective instance.
     *
     * @param templateRef - The template reference that contains the custom column footer layout
     */
    constructor(templateRef) {
        this.templateRef = templateRef;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BoardColumnFooterDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
    static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: BoardColumnFooterDirective, isStandalone: true, selector: "[columnFooterTpt]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BoardColumnFooterDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[columnFooterTpt]',
                    standalone: true
                }]
        }], ctorParameters: () => [{ type: i0.TemplateRef }] });

/**
 * Directive that allows customization of column header templates within board columns.
 *
 * This directive provides the ability to define custom templates for rendering column headers,
 * giving developers full control over the appearance and functionality of column headers
 * including titles, descriptions, actions, and metadata display.
 *
 * @publicApi
 *
 * @example
 * ```html
 * <ng-template columnHeaderTpt let-column="column">
 *   <div class="custom-header">
 *     <h2>{{ column.title }}</h2>
 *     <span class="card-count">{{ column.cards.length }} items</span>
 *     <button (click)="addCard(column)">Add Card</button>
 *   </div>
 * </ng-template>
 * ```
 */
class BoardColumnHeaderDirective {
    templateRef;
    /**
     * Creates a new BoardColumnHeaderDirective instance.
     *
     * @param templateRef - The template reference that contains the custom column header layout
     */
    constructor(templateRef) {
        this.templateRef = templateRef;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BoardColumnHeaderDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
    static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: BoardColumnHeaderDirective, isStandalone: true, selector: "[columnHeaderTpt]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BoardColumnHeaderDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[columnHeaderTpt]',
                    standalone: true
                }]
        }], ctorParameters: () => [{ type: i0.TemplateRef }] });

/**
 * Directive that allows customization of the drag preview (drag image) for cards.
 *
 * This directive defines a custom template for the visual element that follows
 * the cursor when dragging a card. The template receives the dragged card and
 * the source column as context variables.
 *
 * @publicApi
 *
 * @example
 * ```html
 * <hub-board [board]="board">
 *   <ng-template cardDragPreview let-card="card" let-column="column">
 *     <div class="custom-drag-preview">
 *       <i class="bi bi-grip-vertical"></i>
 *       <span>{{ card.title }}</span>
 *     </div>
 *   </ng-template>
 * </hub-board>
 * ```
 */
class CardDragPreviewDirective {
    templateRef;
    /**
     * Creates a new CardDragPreviewDirective instance.
     *
     * @param templateRef - The template reference that contains the custom drag preview layout
     */
    constructor(templateRef) {
        this.templateRef = templateRef;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: CardDragPreviewDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
    static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: CardDragPreviewDirective, isStandalone: true, selector: "[cardDragPreview]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: CardDragPreviewDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[cardDragPreview]',
                    standalone: true
                }]
        }], ctorParameters: () => [{ type: i0.TemplateRef }] });

/**
 * Structural directive used to define a custom placeholder template for cards during drag operations.
 *
 * When a card is being dragged, the placeholder shows where the card will be dropped.
 * By default, an empty space with a dashed border is shown. Use this directive to customize
 * the placeholder appearance.
 *
 * @usageNotes
 *
 * ### Basic usage
 *
 * ```html
 * <hub-board [board]="board">
 *   <ng-template cardPlaceholder let-card="card" let-column="column">
 *     <div class="my-custom-placeholder">
 *       <span>Drop "{{ card?.title }}" here</span>
 *     </div>
 *   </ng-template>
 * </hub-board>
 * ```
 *
 * ### Context variables
 *
 * The template context provides:
 * - `card`: The card being dragged (may be undefined)
 * - `column`: The target column where the card will be dropped
 *
 * @publicApi
 */
class CardPlaceholderDirective {
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: CardPlaceholderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
    static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: CardPlaceholderDirective, isStandalone: true, selector: "[cardPlaceholder]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: CardPlaceholderDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[cardPlaceholder]',
                    standalone: true
                }]
        }] });

/**
 * Directive that allows customization of card templates within board columns.
 *
 * This directive is used to define custom templates for rendering board cards.
 * It provides access to the template reference that can be used by the board component
 * to render cards with custom layouts and styling.
 *
 * @publicApi
 *
 * @example
 * ```html
 * <ng-template cardTpt let-card="item" let-column="column">
 *   <div class="custom-card">
 *     <h3>{{ card.title }}</h3>
 *     <p>{{ card.description }}</p>
 *   </div>
 * </ng-template>
 * ```
 */
class CardTemplateDirective {
    templateRef;
    /**
     * Creates a new CardTemplateDirective instance.
     *
     * @param templateRef - The template reference that contains the custom card layout
     */
    constructor(templateRef) {
        this.templateRef = templateRef;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: CardTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
    static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: CardTemplateDirective, isStandalone: true, selector: "[cardTpt]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: CardTemplateDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[cardTpt]',
                    standalone: true
                }]
        }], ctorParameters: () => [{ type: i0.TemplateRef }] });

/**
 * Directive that allows customization of the drag preview (drag image) for columns.
 *
 * This directive defines a custom template for the visual element that follows
 * the cursor when dragging a column. The template receives the dragged column
 * as a context variable.
 *
 * @publicApi
 *
 * @example
 * ```html
 * <hub-board [board]="board">
 *   <ng-template columnDragPreview let-column="column">
 *     <div class="custom-column-drag-preview">
 *       <i class="bi bi-kanban"></i>
 *       <span>{{ column.title }}</span>
 *       <span class="badge">{{ column.cards.length }} cards</span>
 *     </div>
 *   </ng-template>
 * </hub-board>
 * ```
 */
class ColumnDragPreviewDirective {
    templateRef;
    /**
     * Creates a new ColumnDragPreviewDirective instance.
     *
     * @param templateRef - The template reference that contains the custom drag preview layout
     */
    constructor(templateRef) {
        this.templateRef = templateRef;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ColumnDragPreviewDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
    static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: ColumnDragPreviewDirective, isStandalone: true, selector: "[columnDragPreview]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ColumnDragPreviewDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[columnDragPreview]',
                    standalone: true
                }]
        }], ctorParameters: () => [{ type: i0.TemplateRef }] });

/**
 * Structural directive used to define a custom placeholder template for columns during drag operations.
 *
 * When a column is being dragged, the placeholder shows where the column will be dropped.
 * By default, an empty space with a dashed border is shown. Use this directive to customize
 * the placeholder appearance.
 *
 * @usageNotes
 *
 * ### Basic usage
 *
 * ```html
 * <hub-board [board]="board">
 *   <ng-template columnPlaceholder let-column="column">
 *     <div class="my-custom-placeholder">
 *       <span>Drop "{{ column?.title }}" here</span>
 *     </div>
 *   </ng-template>
 * </hub-board>
 * ```
 *
 * ### Context variables
 *
 * The template context provides:
 * - `column`: The column being dragged (may be undefined)
 *
 * @publicApi
 */
class ColumnPlaceholderDirective {
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ColumnPlaceholderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
    static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: ColumnPlaceholderDirective, isStandalone: true, selector: "[columnPlaceholder]", ngImport: i0 });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: ColumnPlaceholderDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[columnPlaceholder]',
                    standalone: true
                }]
        }] });

/**
 * Array move/transfer helpers used when reordering cards and columns.
 *
 * These come from the shared `ng-hub-ui-utils` drag-and-drop core — a dependency-free,
 * behaviour-identical implementation shared by every ng-hub-ui library — and are re-exported
 * here so the board's public API is unchanged.
 *
 * @publicApi
 */

/**
 * Standalone Kanban-style board component that provides column-based drag-and-drop,
 * custom templates and infinite-scroll detection.
 *
 * @publicApi
 */
class HubBoardComponent {
    /**
     * Reactive input containing the full board definition (columns and cards).
     */
    board = input(/* @ts-ignore */
    ...(ngDevMode ? [undefined, { debugName: "board" }] : /* istanbul ignore next */ []));
    /**
     * Semantic accent applied to the drag/drop placeholder. The built-in values
     * (`primary` / `success` / `danger` / `warning` / `info`) render with the
     * design-system tints; any other string is also accepted — the board reads
     * `--hub-sys-color-<variant>` from the host application, so a custom accent
     * palette interconnects with no changes to this library. Defaults to `primary`.
     */
    variant = input(undefined, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
    /**
     * Inline accent fed to the board styles: `var(--hub-sys-color-<variant>)` for
     * the active variant, or `null` to keep the `primary` default. Keeps the
     * variant set open to any accent token the host application defines.
     */
    groupAccent = computed(() => {
        const variant = this.variant();
        return variant ? `var(--hub-sys-color-${variant})` : null;
    }, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "groupAccent" }] : /* istanbul ignore next */ []));
    /**
     * Pixel threshold used when determining whether a column has reached scroll end.
     * Allows for fractional scroll values across different browsers.
     */
    scrollDetectionPadding = 1;
    /**
     * Internal signal to track column updates and force re-renders.
     */
    _columnsVersion = signal(0, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "_columnsVersion" }] : /* istanbul ignore next */ []));
    /**
     * Derived list of board columns exposed as a signal to the template.
     * Depends on both the board input and internal version counter to ensure
     * re-renders after in-place array mutations.
     */
    columns = computed(() => {
        // Subscribe to version changes to force re-computation after mutations
        this._columnsVersion();
        return this.board()?.columns ?? [];
    }, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
    /**
     * When true, column reordering via drag-and-drop is disabled.
     */
    columnSortingDisabled = input(false, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "columnSortingDisabled" }] : /* istanbul ignore next */ []));
    /**
     * Controls how dragged elements behave visually during drag operations.
     * - 'ghost': Element becomes semi-transparent (50% opacity) but remains visible
     * - 'hide': Element is hidden but still occupies its space
     * - 'collapse': Element is completely hidden and its space is collapsed (default)
     */
    dragBehavior = input('collapse', /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "dragBehavior" }] : /* istanbul ignore next */ []));
    /**
     * Custom card template supplied via the `cardTpt` structural directive.
     */
    cardTpt = contentChild(CardTemplateDirective, { ...(ngDevMode ? { debugName: "cardTpt" } : /* istanbul ignore next */ {}), read: (TemplateRef) });
    /**
     * Custom column header template supplied via the `columnHeaderTpt` structural directive.
     */
    columnHeaderTpt = contentChild(BoardColumnHeaderDirective, { ...(ngDevMode ? { debugName: "columnHeaderTpt" } : /* istanbul ignore next */ {}), read: (TemplateRef) });
    /**
     * Custom column footer template supplied via the `columnFooterTpt` structural directive.
     */
    columnFooterTpt = contentChild(BoardColumnFooterDirective, { ...(ngDevMode ? { debugName: "columnFooterTpt" } : /* istanbul ignore next */ {}), read: (TemplateRef) });
    /**
     * Custom card placeholder template supplied via the `cardPlaceholder` structural directive.
     * Used to customize the appearance of the drop zone when dragging cards.
     */
    cardPlaceholderTpt = contentChild(CardPlaceholderDirective, { ...(ngDevMode ? { debugName: "cardPlaceholderTpt" } : /* istanbul ignore next */ {}), read: (TemplateRef) });
    /**
     * Custom column placeholder template supplied via the `columnPlaceholder` structural directive.
     * Used to customize the appearance of the drop zone when dragging columns.
     */
    columnPlaceholderTpt = contentChild(ColumnPlaceholderDirective, { ...(ngDevMode ? { debugName: "columnPlaceholderTpt" } : /* istanbul ignore next */ {}), read: (TemplateRef) });
    /**
     * Custom card drag preview template supplied via the `cardDragPreview` structural directive.
     * Used to customize the visual element that follows the cursor when dragging cards.
     * The template receives `card` (the dragged card) and `column` (the source column) as context.
     */
    cardDragPreviewTpt = contentChild(CardDragPreviewDirective, { ...(ngDevMode ? { debugName: "cardDragPreviewTpt" } : /* istanbul ignore next */ {}), read: (TemplateRef) });
    /**
     * Custom column drag preview template supplied via the `columnDragPreview` structural directive.
     * Used to customize the visual element that follows the cursor when dragging columns.
     * The template receives `column` (the dragged column) as context.
     */
    columnDragPreviewTpt = contentChild(ColumnDragPreviewDirective, { ...(ngDevMode ? { debugName: "columnDragPreviewTpt" } : /* istanbul ignore next */ {}), read: (TemplateRef) });
    /**
     * Reference to the hidden container where drag preview elements are rendered.
     */
    dragPreviewContainer = viewChild('dragPreviewContainer', /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "dragPreviewContainer" }] : /* istanbul ignore next */ []));
    /**
     * Disposer for the currently active custom drag preview, or `null`.
     */
    dragPreviewDestroy = null;
    /**
     * Emits each time a card is clicked within the board.
     */
    onCardClick = output();
    /**
     * Emits when a card has been repositioned, either within the same column or into another column.
     */
    onCardMoved = output();
    /**
     * Emits when columns are reordered through drag-and-drop.
     */
    onColumnMoved = output();
    /**
     * Emits when a column body is scrolled to its end, enabling infinite-scroll behaviour.
     */
    reachedEnd = output();
    /**
     * Internal drag state tracking.
     */
    dragState = signal(null, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "dragState" }] : /* istanbul ignore next */ []));
    /**
     * Signal for tracking the currently hovered column index during card drag.
     */
    hoveredColumnIndex = signal(null, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "hoveredColumnIndex" }] : /* istanbul ignore next */ []));
    /**
     * Signal for tracking the drop indicator position within a column.
     */
    dropIndicatorIndex = signal(null, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "dropIndicatorIndex" }] : /* istanbul ignore next */ []));
    /**
     * Signal for tracking the column drop indicator position.
     */
    columnDropIndicatorIndex = signal(null, /* @ts-ignore */
    ...(ngDevMode ? [{ debugName: "columnDropIndicatorIndex" }] : /* istanbul ignore next */ []));
    /**
     * Default predicate that allows any card to be dropped into any column.
     *
     * @returns Always `true`, indicating that drop operations are permitted.
     */
    defaultEnterPredicateFn = () => true;
    /**
     * Returns the card currently being dragged, if any.
     *
     * @returns The dragged card or null if no card is being dragged.
     */
    get draggedCard() {
        const state = this.dragState();
        return state?.type === 'card' ? state.item : null;
    }
    /**
     * Returns the column currently being dragged, if any.
     *
     * @returns The dragged column or null if no column is being dragged.
     */
    get draggedColumn() {
        const state = this.dragState();
        return state?.type === 'column' ? state.item : null;
    }
    /**
     * Emits the clicked card through {@link onCardClick}.
     *
     * @param item - The card that triggered the click event.
     */
    cardClick(item) {
        this.onCardClick.emit(item);
    }
    /**
     * Track function for columns to ensure proper re-rendering.
     * Uses column id if available, otherwise falls back to index.
     *
     * @param index - The index of the column.
     * @param column - The column object.
     * @returns A unique identifier for the column.
     */
    trackColumnById(index, column) {
        return column.id ?? index;
    }
    /**
     * Checks if the given column is currently being dragged.
     *
     * @param column - The column to check.
     * @returns Whether the column is being dragged.
     */
    isDraggingColumn(column) {
        const state = this.dragState();
        return state?.type === 'column' && state.item === column;
    }
    /**
     * Checks if the given card is currently being dragged.
     *
     * @param card - The card to check.
     * @returns Whether the card is being dragged.
     */
    isDraggingCard(card) {
        const state = this.dragState();
        return state?.type === 'card' && state.item === card;
    }
    /**
     * Handles the drag start event for columns.
     *
     * @param event - The native drag event.
     * @param column - The column being dragged.
     * @param columnIndex - The index of the column in the board.
     */
    onColumnDragStart(event, column, columnIndex) {
        if (this.columnSortingDisabled() || column.disabled) {
            event.preventDefault();
            return;
        }
        const element = event.currentTarget;
        if (event.dataTransfer) {
            event.dataTransfer.effectAllowed = 'move';
            event.dataTransfer.setData('text/plain', `column:${columnIndex}`);
            // Use custom drag preview if template is provided
            const previewTemplate = this.columnDragPreviewTpt();
            if (previewTemplate) {
                const previewElement = this.createDragPreview(previewTemplate, { column });
                if (previewElement) {
                    event.dataTransfer.setDragImage(previewElement, 0, 0);
                }
            }
        }
        // Set drag state after a micro-delay to allow the browser to capture the drag image
        // before we hide the element
        requestAnimationFrame(() => {
            this.dragState.set({
                type: 'column',
                sourceColumnIndex: columnIndex,
                item: column,
                element
            });
        });
    }
    /**
     * Handles the drag end event for columns.
     *
     * @param _event - The native drag event (unused).
     */
    onColumnDragEnd(_event) {
        this.destroyDragPreview();
        this.columnDropIndicatorIndex.set(null);
        this.dragState.set(null);
    }
    /**
     * Handles the drag over event for the board container.
     * Calculates the drop position based on mouse position relative to columns.
     *
     * @param event - The native drag event.
     */
    onBoardDragOver(event) {
        const state = this.dragState();
        if (!state || state.type !== 'column') {
            return;
        }
        event.preventDefault();
        if (event.dataTransfer) {
            event.dataTransfer.dropEffect = 'move';
        }
        // Find which column we're over based on mouse position
        const boardElement = event.currentTarget;
        const columnElements = boardElement.querySelectorAll('.hub-board__column-container');
        const mouseX = event.clientX;
        let dropIndex = this.columns().length; // Default to end
        for (let i = 0; i < columnElements.length; i++) {
            const columnElement = columnElements[i];
            const rect = columnElement.getBoundingClientRect();
            const columnMiddle = rect.left + rect.width / 2;
            if (mouseX < columnMiddle) {
                dropIndex = i;
                break;
            }
            else if (mouseX < rect.right) {
                dropIndex = i + 1;
                break;
            }
        }
        this.columnDropIndicatorIndex.set(dropIndex);
    }
    /**
     * Handles the drop event for the board container (column reordering).
     *
     * @param event - The native drag event.
     */
    onBoardDrop(event) {
        event.preventDefault();
        const state = this.dragState();
        if (!state || state.type !== 'column') {
            return;
        }
        const dropIndicator = this.columnDropIndicatorIndex();
        if (dropIndicator === null) {
            // No valid drop position - reset state
            this.columnDropIndicatorIndex.set(null);
            this.dragState.set(null);
            return;
        }
        const columns = this.columns();
        const previousIndex = state.sourceColumnIndex;
        // Calculate the actual target index
        // If dropping after the source, we need to subtract 1 because the source will be removed first
        let currentIndex = dropIndicator;
        if (dropIndicator > previousIndex) {
            currentIndex = dropIndicator - 1;
        }
        if (previousIndex !== currentIndex) {
            moveItemInArray(columns, previousIndex, currentIndex);
            // Force re-render by incrementing the version counter
            this._columnsVersion.update((v) => v + 1);
            const dropEvent = {
                previousIndex,
                currentIndex,
                container: { data: columns },
                previousContainer: { data: columns },
                item: { data: state.item },
                isPointerOverContainer: true
            };
            this.onColumnMoved.emit(dropEvent);
        }
        this.columnDropIndicatorIndex.set(null);
        this.dragState.set(null);
    }
    /**
     * Handles the drag start event for cards.
     *
     * @param event - The native drag event.
     * @param card - The card being dragged.
     * @param columnIndex - The index of the column containing the card.
     * @param cardIndex - The index of the card within the column.
     */
    onCardDragStart(event, card, columnIndex, cardIndex) {
        if (card.disabled) {
            event.preventDefault();
            return;
        }
        event.stopPropagation();
        const element = event.currentTarget;
        const sourceColumn = this.columns()[columnIndex];
        if (event.dataTransfer) {
            event.dataTransfer.effectAllowed = 'move';
            event.dataTransfer.setData('text/plain', `card:${columnIndex}:${cardIndex}`);
            // Use custom drag preview if template is provided
            const previewTemplate = this.cardDragPreviewTpt();
            if (previewTemplate) {
                const previewElement = this.createDragPreview(previewTemplate, { card, column: sourceColumn });
                if (previewElement) {
                    event.dataTransfer.setDragImage(previewElement, 0, 0);
                }
            }
        }
        // Set drag state after a micro-delay to allow the browser to capture the drag image
        // before we hide the element
        requestAnimationFrame(() => {
            this.dragState.set({
                type: 'card',
                sourceColumnIndex: columnIndex,
                sourceCardIndex: cardIndex,
                item: card,
                element
            });
        });
    }
    /**
     * Handles the drag end event for cards.
     *
     * @param _event - The native drag event (unused).
     */
    onCardDragEnd(_event) {
        this.destroyDragPreview();
        this.hoveredColumnIndex.set(null);
        this.dropIndicatorIndex.set(null);
        this.dragState.set(null);
    }
    /**
     * Handles the drag over event for column bodies (card drop zones).
     *
     * @param event - The native drag event.
     * @param column - The column being dragged over.
     * @param columnIndex - The index of the column.
     */
    onCardDragOver(event, column, columnIndex) {
        const state = this.dragState();
        if (!state || state.type !== 'card') {
            return;
        }
        // Check if the column accepts this card via predicate
        const predicate = column.predicate ?? this.defaultEnterPredicateFn;
        const dragItem = { data: state.item, element: state.element };
        if (!predicate(dragItem) || column.cardSortingDisabled) {
            return;
        }
        event.preventDefault();
        event.stopPropagation();
        if (event.dataTransfer) {
            event.dataTransfer.dropEffect = 'move';
        }
        this.hoveredColumnIndex.set(columnIndex);
        // Calculate drop position based on mouse Y position
        const containerElement = event.currentTarget;
        const cards = containerElement.querySelectorAll('.hub-board__card:not(.hub-board__card--dragging)');
        const mouseY = event.clientY;
        let dropIndex = column.cards.length;
        for (let i = 0; i < cards.length; i++) {
            const cardElement = cards[i];
            const rect = cardElement.getBoundingClientRect();
            const cardMiddle = rect.top + rect.height / 2;
            if (mouseY < cardMiddle) {
                // Adjust index to account for the dragged card if in same column
                if (state.sourceColumnIndex === columnIndex && state.sourceCardIndex !== undefined) {
                    dropIndex = i <= state.sourceCardIndex ? i : i;
                }
                else {
                    dropIndex = i;
                }
                break;
            }
        }
        this.dropIndicatorIndex.set(dropIndex);
    }
    /**
     * Handles the drag leave event for column bodies.
     *
     * @param event - The native drag event.
     * @param columnIndex - The index of the column being left.
     */
    onCardDragLeave(event, columnIndex) {
        const relatedTarget = event.relatedTarget;
        const currentTarget = event.currentTarget;
        // Only clear if we're actually leaving the container (not entering a child)
        if (!relatedTarget || !currentTarget.contains(relatedTarget)) {
            if (this.hoveredColumnIndex() === columnIndex) {
                this.hoveredColumnIndex.set(null);
                this.dropIndicatorIndex.set(null);
            }
        }
    }
    /**
     * Handles the drop event for cards.
     *
     * @param event - The native drag event.
     * @param column - The target column.
     * @param columnIndex - The index of the target column.
     */
    onCardDrop(event, column, columnIndex) {
        event.preventDefault();
        event.stopPropagation();
        const state = this.dragState();
        if (!state || state.type !== 'card' || state.sourceCardIndex === undefined) {
            return;
        }
        const columns = this.columns();
        const sourceColumn = columns[state.sourceColumnIndex];
        const targetColumn = column;
        const previousIndex = state.sourceCardIndex;
        let currentIndex = this.dropIndicatorIndex() ?? targetColumn.cards.length;
        // Adjust for same-column moves
        if (state.sourceColumnIndex === columnIndex && previousIndex < currentIndex) {
            currentIndex--;
        }
        const isSameContainer = state.sourceColumnIndex === columnIndex;
        if (isSameContainer) {
            if (previousIndex !== currentIndex) {
                moveItemInArray(targetColumn.cards, previousIndex, currentIndex);
            }
        }
        else {
            transferArrayItem(sourceColumn.cards, targetColumn.cards, previousIndex, currentIndex);
        }
        // Force re-render by incrementing the version counter
        this._columnsVersion.update((v) => v + 1);
        const dropEvent = {
            previousIndex,
            currentIndex,
            container: { data: targetColumn },
            previousContainer: { data: sourceColumn },
            item: { data: state.item },
            isPointerOverContainer: true
        };
        this.onCardMoved.emit(dropEvent);
        this.hoveredColumnIndex.set(null);
        this.dropIndicatorIndex.set(null);
        this.dragState.set(null);
    }
    /**
     * Determines if a column should show the drop indicator.
     *
     * @param columnIndex - The index position to check for the drop indicator.
     * @returns Whether the drop indicator should be shown at this position.
     */
    shouldShowColumnDropIndicator(columnIndex) {
        const state = this.dragState();
        if (state?.type !== 'column') {
            return false;
        }
        return this.columnDropIndicatorIndex() === columnIndex;
    }
    /**
     * Determines if a card drop indicator should be shown at a specific position.
     *
     * @param columnIndex - The index of the column.
     * @param cardIndex - The index where the indicator would appear.
     * @returns Whether the drop indicator should be shown.
     */
    shouldShowCardDropIndicator(columnIndex, cardIndex) {
        const state = this.dragState();
        if (state?.type !== 'card') {
            return false;
        }
        return this.hoveredColumnIndex() === columnIndex && this.dropIndicatorIndex() === cardIndex;
    }
    /**
     * Emits {@link reachedEnd} once a column body is scrolled to its bottom.
     *
     * @param index - Index of the scrolled column within the board.
     * @param event - Browser scroll event originating from the column body element.
     */
    onScroll(index, event) {
        const el = event.target;
        if (!el) {
            return;
        }
        const scrolledToBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - this.scrollDetectionPadding;
        if (!scrolledToBottom) {
            return;
        }
        const column = this.board()?.columns?.[index];
        if (!column) {
            return;
        }
        this.reachedEnd.emit({
            index,
            data: column
        });
    }
    /**
     * Creates a custom drag preview element from a template and renders it in a hidden container.
     * The element must be in the DOM for setDragImage to work properly.
     *
     * @param template - The template to render as the drag preview.
     * @param context - The context to pass to the template.
     * @returns The native HTML element to use as the drag image, or null if creation failed.
     */
    createDragPreview(template, context) {
        // Destroy any existing preview first.
        this.destroyDragPreview();
        const container = this.dragPreviewContainer();
        if (!container) {
            return null;
        }
        // Render the template into the hidden container using the shared drag-image helper.
        const image = createNativeDragImage(template, context, container.nativeElement);
        if (!image) {
            return null;
        }
        this.dragPreviewDestroy = image.destroy;
        return image.node;
    }
    /**
     * Destroys the current drag preview view and cleans up related resources. The disposer
     * returned by `createNativeDragImage` removes both the embedded view and the rendered
     * nodes, so no manual container clearing is required.
     */
    destroyDragPreview() {
        this.dragPreviewDestroy?.();
        this.dragPreviewDestroy = null;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubBoardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
    static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: HubBoardComponent, isStandalone: true, selector: "hub-board, hub-ui-board", inputs: { board: { classPropertyName: "board", publicName: "board", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, columnSortingDisabled: { classPropertyName: "columnSortingDisabled", publicName: "columnSortingDisabled", isSignal: true, isRequired: false, transformFunction: null }, dragBehavior: { classPropertyName: "dragBehavior", publicName: "dragBehavior", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onCardClick: "onCardClick", onCardMoved: "onCardMoved", onColumnMoved: "onColumnMoved", reachedEnd: "reachedEnd" }, host: { properties: { "attr.data-variant": "variant() ?? null", "style.--hub-board-accent": "groupAccent()" }, classAttribute: "hub-board" }, queries: [{ propertyName: "cardTpt", first: true, predicate: CardTemplateDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "columnHeaderTpt", first: true, predicate: BoardColumnHeaderDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "columnFooterTpt", first: true, predicate: BoardColumnFooterDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "cardPlaceholderTpt", first: true, predicate: CardPlaceholderDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "columnPlaceholderTpt", first: true, predicate: ColumnPlaceholderDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "cardDragPreviewTpt", first: true, predicate: CardDragPreviewDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "columnDragPreviewTpt", first: true, predicate: ColumnDragPreviewDirective, descendants: true, read: TemplateRef, isSignal: true }], viewQueries: [{ propertyName: "dragPreviewContainer", first: true, predicate: ["dragPreviewContainer"], descendants: true, isSignal: true }], ngImport: i0, template: "@if (columns().length) {\n<div class=\"hub-board__drop-list\" (dragover)=\"onBoardDragOver($event)\" (drop)=\"onBoardDrop($event)\">\n\t<div class=\"hub-board__columns\">\n\t\t@for (column of columns(); let index = $index; track trackColumnById($index, column)) {\n\t\t<!-- Column placeholder (before column) -->\n\t\t@if (shouldShowColumnDropIndicator(index)) {\n\t\t<div class=\"hub-board__column-placeholder\">\n\t\t\t@if (columnPlaceholderTpt()) {\n\t\t\t<ng-container\n\t\t\t\t[ngTemplateOutlet]=\"columnPlaceholderTpt()!\"\n\t\t\t\t[ngTemplateOutletContext]=\"{ column: draggedColumn }\"\n\t\t\t></ng-container>\n\t\t\t}\n\t\t</div>\n\t\t}\n\t\t<div\n\t\t\tclass=\"hub-board__column-container\"\n\t\t\t[class.hub-board__column-container--disabled]=\"column.disabled\"\n\t\t\t[class.hub-board__column-container--dragging-ghost]=\"isDraggingColumn(column) && dragBehavior() === 'ghost'\"\n\t\t\t[class.hub-board__column-container--dragging-hide]=\"isDraggingColumn(column) && dragBehavior() === 'hide'\"\n\t\t\t[class.hub-board__column-container--dragging-collapse]=\"isDraggingColumn(column) && dragBehavior() === 'collapse'\"\n\t\t\t[attr.draggable]=\"!columnSortingDisabled() && !column.disabled\"\n\t\t\t[attr.data-column-index]=\"index\"\n\t\t\t(dragstart)=\"onColumnDragStart($event, column, index)\"\n\t\t\t(dragend)=\"onColumnDragEnd($event)\"\n\t\t>\n\t\t\t<div class=\"hub-board__column\" [ngClass]=\"column.classlist\" [style]=\"column.style\">\n\t\t\t\t<div class=\"hub-board__column-header\">\n\t\t\t\t\t<ng-container\n\t\t\t\t\t\t[ngTemplateOutlet]=\"columnHeaderTpt() || defaultColumnHeaderTpt\"\n\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ column: column }\"\n\t\t\t\t\t>\n\t\t\t\t\t</ng-container>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"hub-board__column-body\"\n\t\t\t\t\t[class.hub-board__column-body--drag-over]=\"hoveredColumnIndex() === index\"\n\t\t\t\t\t(scroll)=\"onScroll(index, $event)\"\n\t\t\t\t\t(dragover)=\"onCardDragOver($event, column, index)\"\n\t\t\t\t\t(dragleave)=\"onCardDragLeave($event, index)\"\n\t\t\t\t\t(drop)=\"onCardDrop($event, column, index)\"\n\t\t\t\t>\n\t\t\t\t\t@for (card of column.cards; let cardIndex = $index; track card) {\n\t\t\t\t\t<!-- Card placeholder (before card) -->\n\t\t\t\t\t@if (shouldShowCardDropIndicator(index, cardIndex)) {\n\t\t\t\t\t<div class=\"hub-board__card-placeholder\">\n\t\t\t\t\t\t@if (cardPlaceholderTpt()) {\n\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t[ngTemplateOutlet]=\"cardPlaceholderTpt()!\"\n\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ card: draggedCard, column: column }\"\n\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"hub-board__card\"\n\t\t\t\t\t\t[class.hub-board__card--disabled]=\"card.disabled\"\n\t\t\t\t\t\t[class.hub-board__card--dragging-ghost]=\"isDraggingCard(card) && dragBehavior() === 'ghost'\"\n\t\t\t\t\t\t[class.hub-board__card--dragging-hide]=\"isDraggingCard(card) && dragBehavior() === 'hide'\"\n\t\t\t\t\t\t[class.hub-board__card--dragging-collapse]=\"isDraggingCard(card) && dragBehavior() === 'collapse'\"\n\t\t\t\t\t\t[attr.draggable]=\"!card.disabled && !column.cardSortingDisabled\"\n\t\t\t\t\t\t(dragstart)=\"onCardDragStart($event, card, index, cardIndex)\"\n\t\t\t\t\t\t(dragend)=\"onCardDragEnd($event)\"\n\t\t\t\t\t\t(click)=\"cardClick(card)\"\n\t\t\t\t\t\t(mousedown)=\"card.disabled ? $event.stopPropagation() : null\"\n\t\t\t\t\t\t[ngClass]=\"card.classlist\"\n\t\t\t\t\t\t[style]=\"card.style\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<div class=\"hub-board__card-body\">\n\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"cardTpt() || defaultCardTpt\"\n\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{\n\t\t\t\t\t\t\t\t\titem: card,\n\t\t\t\t\t\t\t\t\tcolumn\n\t\t\t\t\t\t\t\t}\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t</ng-container>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t\t<!-- Card placeholder at the end -->\n\t\t\t\t\t@if (shouldShowCardDropIndicator(index, column.cards.length)) {\n\t\t\t\t\t<div class=\"hub-board__card-placeholder\">\n\t\t\t\t\t\t@if (cardPlaceholderTpt()) {\n\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t[ngTemplateOutlet]=\"cardPlaceholderTpt()!\"\n\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ card: draggedCard, column: column }\"\n\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\n\t\t\t\t@if (columnFooterTpt()) {\n\t\t\t\t<div class=\"hub-board__column-footer\">\n\t\t\t\t\t<ng-container\n\t\t\t\t\t\t[ngTemplateOutlet]=\"columnFooterTpt() ?? null\"\n\t\t\t\t\t\t[ngTemplateOutletContext]=\"{\n\t\t\t\t\t\t\tcolumn: column\n\t\t\t\t\t\t}\"\n\t\t\t\t\t>\n\t\t\t\t\t</ng-container>\n\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</div>\n\t\t}\n\t\t<!-- Column placeholder at the end -->\n\t\t@if (shouldShowColumnDropIndicator(columns().length)) {\n\t\t<div class=\"hub-board__column-placeholder\">\n\t\t\t@if (columnPlaceholderTpt()) {\n\t\t\t<ng-container\n\t\t\t\t[ngTemplateOutlet]=\"columnPlaceholderTpt()!\"\n\t\t\t\t[ngTemplateOutletContext]=\"{ column: draggedColumn }\"\n\t\t\t></ng-container>\n\t\t\t}\n\t\t</div>\n\t\t}\n\t</div>\n</div>\n}\n\n<ng-template #defaultCardTpt let-item=\"item\">\n\t<h6 class=\"hub-board__card-title\">{{ item.title }}</h6>\n\t<p class=\"hub-board__card-subtitle\">{{ item.description }}</p>\n</ng-template>\n\n<ng-template #defaultColumnHeaderTpt let-column=\"column\">\n\t<div class=\"d-flex flex-column\">\n\t\t<h5 class=\"hub-board__column-header-title\">\n\t\t\t{{ column.title }}\n\t\t</h5>\n\t\t<h6 class=\"hub-board__column-header-subtitle\">\n\t\t\t{{ column.description }}\n\t\t</h6>\n\t</div>\n</ng-template>\n\n<!-- Hidden container for rendering drag preview elements -->\n<div #dragPreviewContainer class=\"hub-board__drag-preview-container\" aria-hidden=\"true\"></div>\n", styles: [":root,:host{--hub-board-container-color: var(--hub-sys-text-primary, #212529);--hub-board-container-bg: var(--hub-sys-surface-page, #fff);--hub-board-border-width: var(--hub-ref-border-width, 1px);--hub-board-border-color: var(--hub-sys-border-color-default, #dee2e6);--hub-board-border-radius: var(--hub-ref-radius-md, .375rem);--hub-board-columns-gap: var(--hub-ref-space-3, 1rem);--hub-board-column-width: 256px;--hub-board-column-min-height: 200px;--hub-board-column-body-min-height: 128px;--hub-board-column-body-gap: var(--hub-ref-space-3, 1rem);--hub-board-column-spacer-y: .75rem;--hub-board-column-spacer-x: var(--hub-ref-space-3, 1rem);--hub-board-column-border-width: var(--hub-board-border-width);--hub-board-column-border-color: var(--hub-board-border-color);--hub-board-column-border-radius: var(--hub-board-border-radius);--hub-board-column-box-shadow: none;--hub-board-column-inner-border-radius: var(--hub-board-border-radius);--hub-board-column-cap-padding-y: var(--hub-ref-space-2, .5rem);--hub-board-column-cap-padding-x: var(--hub-ref-space-3, 1rem);--hub-board-column-cap-bg: var(--hub-sys-surface-elevated, #f8f9fa);--hub-board-column-cap-color: inherit;--hub-board-column-header-padding-y: var(--hub-board-column-cap-padding-y);--hub-board-column-header-padding-x: var(--hub-board-column-cap-padding-x);--hub-board-column-header-bg: var(--hub-board-column-cap-bg);--hub-board-column-header-color: var(--hub-board-column-cap-color);--hub-board-column-footer-padding-y: var(--hub-board-column-cap-padding-y);--hub-board-column-footer-padding-x: var(--hub-board-column-cap-padding-x);--hub-board-column-footer-bg: var(--hub-board-column-cap-bg);--hub-board-column-footer-color: var(--hub-board-column-cap-color);--hub-board-column-body-padding-y: var(--hub-board-column-spacer-y);--hub-board-column-body-padding-x: var(--hub-board-column-spacer-x);--hub-board-column-header-title-color: inherit;--hub-board-column-header-title-spacer-y: var(--hub-ref-space-2, .5rem);--hub-board-column-header-subtitle-color: var(--hub-sys-text-muted, #6c757d);--hub-board-column-height: 100%;--hub-board-column-color: var(--hub-board-container-color);--hub-board-column-bg: var(--hub-board-container-bg);--hub-board-card-spacer-y: .75rem;--hub-board-card-spacer-x: var(--hub-ref-space-3, 1rem);--hub-board-card-title-spacer-y: var(--hub-ref-space-2, .5rem);--hub-board-card-title-color: inherit;--hub-board-card-subtitle-color: var(--hub-sys-text-muted, #6c757d);--hub-board-card-border-width: var(--hub-board-border-width);--hub-board-card-border-color: var(--hub-board-border-color);--hub-board-card-border-radius: var(--hub-board-border-radius);--hub-board-card-box-shadow: none;--hub-board-card-inner-border-radius: calc( var(--hub-board-card-border-radius) - var(--hub-board-card-border-width) );--hub-board-card-cap-padding-y: var(--hub-ref-space-2, .5rem);--hub-board-card-cap-padding-x: var(--hub-ref-space-3, 1rem);--hub-board-card-cap-bg: var(--hub-sys-surface-elevated, #f8f9fa);--hub-board-card-cap-color: inherit;--hub-board-card-padding-y: var(--hub-board-card-spacer-y);--hub-board-card-padding-x: var(--hub-board-card-spacer-x);--hub-board-card-header-padding-y: var(--hub-board-card-cap-padding-y);--hub-board-card-header-padding-x: var(--hub-board-card-cap-padding-x);--hub-board-card-header-bg: var(--hub-board-card-cap-bg);--hub-board-card-header-color: var(--hub-board-card-cap-color);--hub-board-card-footer-padding-y: var(--hub-board-card-cap-padding-y);--hub-board-card-footer-padding-x: var(--hub-board-card-cap-padding-x);--hub-board-card-footer-bg: var(--hub-board-card-cap-bg);--hub-board-card-footer-color: var(--hub-board-card-cap-color);--hub-board-card-height: auto;--hub-board-card-color: var(--hub-board-container-color);--hub-board-card-bg: var(--hub-board-container-bg);--hub-board-drag-transition: transform .25s cubic-bezier(0, 0, .2, 1);--hub-board-accent: var(--hub-sys-color-primary, #0d6efd);--hub-board-accent-emphasis: color-mix(in oklch, var(--hub-board-accent) 80%, var(--hub-sys-color-ink, #212529));--hub-board-accent-subtle: color-mix(in oklch, var(--hub-board-accent) 12%, var(--hub-sys-surface-page, #fff));--hub-board-accent-on: oklch(from var(--hub-board-accent) clamp(0, (.62 - l) * 1000, 1) 0 h);--hub-board-placeholder-border-color: var(--hub-board-accent);--hub-board-placeholder-border-width: 2px;--hub-board-placeholder-border-style: dashed;--hub-board-placeholder-bg: var(--hub-board-accent-subtle);--hub-board-placeholder-min-height: 60px}:host{display:block;overflow:auto;width:100%;height:100%}.hub-board__drop-list{height:100%}.hub-board__columns{display:flex;flex-wrap:nowrap;width:100%;height:100%;gap:var(--hub-board-columns-gap, var(--hub-ref-space-3, 1rem))}.hub-board__columns .hub-board__column-container{height:100%}.hub-board__column-container{cursor:grab}.hub-board__column-container--dragging-ghost{opacity:.4;pointer-events:none}.hub-board__column-container--dragging-hide{visibility:hidden;opacity:0;pointer-events:none}.hub-board__column-container--dragging-collapse{display:none}.hub-board__column-container--disabled{cursor:default}.hub-board__column-container[draggable=true]{cursor:grab}.hub-board__column-container[draggable=true]:active{cursor:grabbing}.hub-board__column-container[draggable=false]{cursor:default}.hub-board__column-placeholder{width:var(--hub-board-column-width, 256px);min-width:var(--hub-board-column-width, 256px);height:100%;min-height:var(--hub-board-column-min-height, 200px);border:var(--hub-board-placeholder-border-width, 2px) var(--hub-board-placeholder-border-style, dashed) var(--hub-board-placeholder-border-color, var(--hub-sys-color-primary, #0d6efd));border-radius:var(--hub-board-column-border-radius, var(--hub-ref-radius-md, .375rem));background-color:var(--hub-board-placeholder-bg, var(--hub-sys-color-primary-subtle, rgba(13, 110, 253, .05)));flex-shrink:0;display:flex;align-items:center;justify-content:center;animation:hub-board-placeholder-fade-in .15s ease-out}.hub-board__column{position:relative;display:flex;flex-direction:column;min-width:0;height:var(--hub-board-column-height, 100%);max-height:100%;width:var(--hub-board-column-width, 256px);color:var(--hub-board-container-color, var(--hub-sys-text-primary, #212529));word-wrap:break-word;background-color:var(--hub-board-column-bg, var(--hub-sys-surface-page, #fff));background-clip:border-box;border:var(--hub-board-column-border-width, var(--hub-ref-border-width, 1px)) solid var(--hub-board-column-border-color, var(--hub-sys-border-color-default, #dee2e6));border-radius:var(--hub-board-column-border-radius, var(--hub-ref-radius-md, .375rem));box-shadow:var(--hub-board-column-box-shadow, none)}.hub-board__column-body{flex:1 1 auto;padding:var(--hub-board-column-body-padding-y) var(--hub-board-column-body-padding-x);color:var(--hub-board-column-color, var(--hub-board-container-color));overflow:auto;display:flex;flex-direction:column;gap:var(--hub-board-column-body-gap, var(--hub-ref-space-3, 1rem));min-height:var(--hub-board-column-body-min-height, 128px)}.hub-board__column-body--drag-over{background-color:var(--hub-board-placeholder-bg, var(--hub-sys-color-primary-subtle, rgba(13, 110, 253, .05)))}.hub-board__column-header{padding:var(--hub-board-column-header-padding-y) var(--hub-board-column-header-padding-x);margin:0;color:var(--hub-board-column-header-color);background-color:var(--hub-board-column-header-bg);border-bottom:var(--hub-board-column-border-width, var(--hub-ref-border-width, 1px)) solid var(--hub-board-column-border-color, var(--hub-sys-border-color-default, #dee2e6))}.hub-board__column-header-title{margin-bottom:var(--hub-board-column-header-title-spacer-y, var(--hub-ref-space-2, .5rem));color:var(--hub-board-column-header-title-color, inherit)}.hub-board__column-header-subtitle{margin-top:calc(-.5 * var(--hub-board-column-header-title-spacer-y, var(--hub-ref-space-2, .5rem)));margin-bottom:0;color:var(--hub-board-column-header-subtitle-color, var(--hub-sys-text-muted, #6c757d))}.hub-board__column-header:first-child{border-top-left-radius:var(--hub-board-column-inner-border-radius, var(--hub-board-border-radius, var(--hub-ref-radius-md, .375rem)));border-top-right-radius:var(--hub-board-column-inner-border-radius, var(--hub-board-border-radius, var(--hub-ref-radius-md, .375rem)));border-bottom-right-radius:0;border-bottom-left-radius:0}.hub-board__column-footer{padding:var(--hub-board-column-footer-padding-y) var(--hub-board-column-footer-padding-x);color:var(--hub-board-column-footer-color);background-color:var(--hub-board-column-footer-bg);border-top:var(--hub-board-column-border-width, var(--hub-ref-border-width, 1px)) solid var(--hub-board-column-border-color, var(--hub-sys-border-color-default, #dee2e6))}.hub-board__column-footer:last-child{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:var(--hub-board-column-inner-border-radius, var(--hub-board-border-radius, var(--hub-ref-radius-md, .375rem)));border-bottom-left-radius:var(--hub-board-column-inner-border-radius, var(--hub-board-border-radius, var(--hub-ref-radius-md, .375rem)))}.hub-board__card{position:relative;display:flex;flex-direction:column;min-width:0;height:var(--hub-board-card-height, auto);color:var(--hub-board-container-color, var(--hub-sys-text-primary, #212529));word-wrap:break-word;background-color:var(--hub-board-card-bg, var(--hub-sys-surface-page, #fff));background-clip:border-box;border:var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px)) solid var(--hub-board-card-border-color, var(--hub-sys-border-color-default, #dee2e6));border-radius:var(--hub-board-card-border-radius, var(--hub-board-border-radius, var(--hub-ref-radius-md, .375rem)));box-shadow:var(--hub-board-card-box-shadow, none);cursor:grab;-webkit-user-select:none;user-select:none;transition:var(--hub-board-drag-transition, transform .25s cubic-bezier(0, 0, .2, 1))}.hub-board__card--dragging-ghost{opacity:.4;pointer-events:none}.hub-board__card--dragging-hide{visibility:hidden;opacity:0;pointer-events:none}.hub-board__card--dragging-collapse{display:none}.hub-board__card--disabled{cursor:default;opacity:.6}.hub-board__card[draggable=true]{cursor:grab}.hub-board__card[draggable=true]:active{cursor:grabbing}.hub-board__card[draggable=false]{cursor:default}.hub-board__card>hr{margin-right:0;margin-left:0}.hub-board__card>.list-group{border-top:inherit;border-bottom:inherit}.hub-board__card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))));border-top-right-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))))}.hub-board__card>.list-group:last-child{border-bottom-width:0;border-bottom-left-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))));border-bottom-right-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))))}.hub-board__card>.hub-board__card-header+.list-group,.hub-board__card>.list-group+.hub-board__card-footer{border-top:0}.hub-board__card-body{flex:1 1 auto;padding:var(--hub-board-card-padding-y) var(--hub-board-card-padding-x);color:var(--hub-board-card-color, var(--hub-board-container-color))}.hub-board__card-title{margin-bottom:var(--hub-board-card-title-spacer-y, var(--hub-ref-space-2, .5rem));color:var(--hub-board-card-title-color, inherit)}.hub-board__card-subtitle{margin-top:calc(-.5 * var(--hub-board-card-title-spacer-y, var(--hub-ref-space-2, .5rem)));margin-bottom:0;color:var(--hub-board-card-subtitle-color, var(--hub-sys-text-muted, #6c757d))}.hub-board__card-header{padding:var(--hub-board-card-header-padding-y) var(--hub-board-card-header-padding-x);color:var(--hub-board-card-header-color);background-color:var(--hub-board-card-header-bg);border-bottom:var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px)) solid var(--hub-board-card-border-color, var(--hub-sys-border-color-default, #dee2e6))}.hub-board__card-header:first-child{border-top-left-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))));border-top-right-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))))}.hub-board__card-footer{padding:var(--hub-board-card-footer-padding-y) var(--hub-board-card-footer-padding-x);color:var(--hub-board-card-footer-color);background-color:var(--hub-board-card-footer-bg);border-top:var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px)) solid var(--hub-board-card-border-color, var(--hub-sys-border-color-default, #dee2e6))}.hub-board__card-footer:last-child{border-bottom-left-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))));border-bottom-right-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))))}.hub-board__card-placeholder{min-height:var(--hub-board-placeholder-min-height, 60px);border:var(--hub-board-placeholder-border-width, 2px) var(--hub-board-placeholder-border-style, dashed) var(--hub-board-placeholder-border-color, var(--hub-sys-color-primary, #0d6efd));border-radius:var(--hub-board-card-border-radius, var(--hub-board-border-radius, var(--hub-ref-radius-md, .375rem)));background-color:var(--hub-board-placeholder-bg, var(--hub-sys-color-primary-subtle, rgba(13, 110, 253, .05)));display:flex;align-items:center;justify-content:center;animation:hub-board-placeholder-fade-in .15s ease-out}.hub-board__drag-preview-container{position:fixed;top:-9999px;left:-9999px;pointer-events:none;z-index:-1}@keyframes hub-board-placeholder-fade-in{0%{opacity:0;transform:scaleY(.8)}to{opacity:1;transform:scaleY(1)}}:host([data-variant=primary]),.hub-board[data-variant=primary]{--hub-board-accent: var(--hub-sys-color-primary)}:host([data-variant=secondary]),.hub-board[data-variant=secondary]{--hub-board-accent: var(--hub-sys-color-secondary)}:host([data-variant=success]),.hub-board[data-variant=success]{--hub-board-accent: var(--hub-sys-color-success)}:host([data-variant=danger]),.hub-board[data-variant=danger]{--hub-board-accent: var(--hub-sys-color-danger)}:host([data-variant=warning]),.hub-board[data-variant=warning]{--hub-board-accent: var(--hub-sys-color-warning)}:host([data-variant=info]),.hub-board[data-variant=info]{--hub-board-accent: var(--hub-sys-color-info)}:host([data-variant=neutral]),.hub-board[data-variant=neutral]{--hub-board-accent: var(--hub-sys-color-neutral)}:host([data-variant=light]),.hub-board[data-variant=light]{--hub-board-accent: var(--hub-sys-color-light)}:host([data-variant=dark]),.hub-board[data-variant=dark]{--hub-board-accent: var(--hub-sys-color-dark)}:host([data-variant]),.hub-board[data-variant]{--hub-board-accent-emphasis: color-mix(in oklch, var(--hub-board-accent) 80%, var(--hub-sys-color-ink, #212529));--hub-board-accent-subtle: color-mix(in oklch, var(--hub-board-accent) 12%, var(--hub-sys-surface-page, #fff));--hub-board-accent-on: oklch(from var(--hub-board-accent) clamp(0, (.62 - l) * 1000, 1) 0 h)}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubBoardComponent, decorators: [{
            type: Component,
            args: [{ selector: 'hub-board, hub-ui-board', imports: [NgClass, NgTemplateOutlet], host: {
                        class: 'hub-board',
                        '[attr.data-variant]': 'variant() ?? null',
                        '[style.--hub-board-accent]': 'groupAccent()'
                    }, template: "@if (columns().length) {\n<div class=\"hub-board__drop-list\" (dragover)=\"onBoardDragOver($event)\" (drop)=\"onBoardDrop($event)\">\n\t<div class=\"hub-board__columns\">\n\t\t@for (column of columns(); let index = $index; track trackColumnById($index, column)) {\n\t\t<!-- Column placeholder (before column) -->\n\t\t@if (shouldShowColumnDropIndicator(index)) {\n\t\t<div class=\"hub-board__column-placeholder\">\n\t\t\t@if (columnPlaceholderTpt()) {\n\t\t\t<ng-container\n\t\t\t\t[ngTemplateOutlet]=\"columnPlaceholderTpt()!\"\n\t\t\t\t[ngTemplateOutletContext]=\"{ column: draggedColumn }\"\n\t\t\t></ng-container>\n\t\t\t}\n\t\t</div>\n\t\t}\n\t\t<div\n\t\t\tclass=\"hub-board__column-container\"\n\t\t\t[class.hub-board__column-container--disabled]=\"column.disabled\"\n\t\t\t[class.hub-board__column-container--dragging-ghost]=\"isDraggingColumn(column) && dragBehavior() === 'ghost'\"\n\t\t\t[class.hub-board__column-container--dragging-hide]=\"isDraggingColumn(column) && dragBehavior() === 'hide'\"\n\t\t\t[class.hub-board__column-container--dragging-collapse]=\"isDraggingColumn(column) && dragBehavior() === 'collapse'\"\n\t\t\t[attr.draggable]=\"!columnSortingDisabled() && !column.disabled\"\n\t\t\t[attr.data-column-index]=\"index\"\n\t\t\t(dragstart)=\"onColumnDragStart($event, column, index)\"\n\t\t\t(dragend)=\"onColumnDragEnd($event)\"\n\t\t>\n\t\t\t<div class=\"hub-board__column\" [ngClass]=\"column.classlist\" [style]=\"column.style\">\n\t\t\t\t<div class=\"hub-board__column-header\">\n\t\t\t\t\t<ng-container\n\t\t\t\t\t\t[ngTemplateOutlet]=\"columnHeaderTpt() || defaultColumnHeaderTpt\"\n\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ column: column }\"\n\t\t\t\t\t>\n\t\t\t\t\t</ng-container>\n\t\t\t\t</div>\n\t\t\t\t<div\n\t\t\t\t\tclass=\"hub-board__column-body\"\n\t\t\t\t\t[class.hub-board__column-body--drag-over]=\"hoveredColumnIndex() === index\"\n\t\t\t\t\t(scroll)=\"onScroll(index, $event)\"\n\t\t\t\t\t(dragover)=\"onCardDragOver($event, column, index)\"\n\t\t\t\t\t(dragleave)=\"onCardDragLeave($event, index)\"\n\t\t\t\t\t(drop)=\"onCardDrop($event, column, index)\"\n\t\t\t\t>\n\t\t\t\t\t@for (card of column.cards; let cardIndex = $index; track card) {\n\t\t\t\t\t<!-- Card placeholder (before card) -->\n\t\t\t\t\t@if (shouldShowCardDropIndicator(index, cardIndex)) {\n\t\t\t\t\t<div class=\"hub-board__card-placeholder\">\n\t\t\t\t\t\t@if (cardPlaceholderTpt()) {\n\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t[ngTemplateOutlet]=\"cardPlaceholderTpt()!\"\n\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ card: draggedCard, column: column }\"\n\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t\t<div\n\t\t\t\t\t\tclass=\"hub-board__card\"\n\t\t\t\t\t\t[class.hub-board__card--disabled]=\"card.disabled\"\n\t\t\t\t\t\t[class.hub-board__card--dragging-ghost]=\"isDraggingCard(card) && dragBehavior() === 'ghost'\"\n\t\t\t\t\t\t[class.hub-board__card--dragging-hide]=\"isDraggingCard(card) && dragBehavior() === 'hide'\"\n\t\t\t\t\t\t[class.hub-board__card--dragging-collapse]=\"isDraggingCard(card) && dragBehavior() === 'collapse'\"\n\t\t\t\t\t\t[attr.draggable]=\"!card.disabled && !column.cardSortingDisabled\"\n\t\t\t\t\t\t(dragstart)=\"onCardDragStart($event, card, index, cardIndex)\"\n\t\t\t\t\t\t(dragend)=\"onCardDragEnd($event)\"\n\t\t\t\t\t\t(click)=\"cardClick(card)\"\n\t\t\t\t\t\t(mousedown)=\"card.disabled ? $event.stopPropagation() : null\"\n\t\t\t\t\t\t[ngClass]=\"card.classlist\"\n\t\t\t\t\t\t[style]=\"card.style\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<div class=\"hub-board__card-body\">\n\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"cardTpt() || defaultCardTpt\"\n\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{\n\t\t\t\t\t\t\t\t\titem: card,\n\t\t\t\t\t\t\t\t\tcolumn\n\t\t\t\t\t\t\t\t}\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t</ng-container>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t\t<!-- Card placeholder at the end -->\n\t\t\t\t\t@if (shouldShowCardDropIndicator(index, column.cards.length)) {\n\t\t\t\t\t<div class=\"hub-board__card-placeholder\">\n\t\t\t\t\t\t@if (cardPlaceholderTpt()) {\n\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t[ngTemplateOutlet]=\"cardPlaceholderTpt()!\"\n\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ card: draggedCard, column: column }\"\n\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t}\n\t\t\t\t\t</div>\n\t\t\t\t\t}\n\t\t\t\t</div>\n\n\t\t\t\t@if (columnFooterTpt()) {\n\t\t\t\t<div class=\"hub-board__column-footer\">\n\t\t\t\t\t<ng-container\n\t\t\t\t\t\t[ngTemplateOutlet]=\"columnFooterTpt() ?? null\"\n\t\t\t\t\t\t[ngTemplateOutletContext]=\"{\n\t\t\t\t\t\t\tcolumn: column\n\t\t\t\t\t\t}\"\n\t\t\t\t\t>\n\t\t\t\t\t</ng-container>\n\t\t\t\t</div>\n\t\t\t\t}\n\t\t\t</div>\n\t\t</div>\n\t\t}\n\t\t<!-- Column placeholder at the end -->\n\t\t@if (shouldShowColumnDropIndicator(columns().length)) {\n\t\t<div class=\"hub-board__column-placeholder\">\n\t\t\t@if (columnPlaceholderTpt()) {\n\t\t\t<ng-container\n\t\t\t\t[ngTemplateOutlet]=\"columnPlaceholderTpt()!\"\n\t\t\t\t[ngTemplateOutletContext]=\"{ column: draggedColumn }\"\n\t\t\t></ng-container>\n\t\t\t}\n\t\t</div>\n\t\t}\n\t</div>\n</div>\n}\n\n<ng-template #defaultCardTpt let-item=\"item\">\n\t<h6 class=\"hub-board__card-title\">{{ item.title }}</h6>\n\t<p class=\"hub-board__card-subtitle\">{{ item.description }}</p>\n</ng-template>\n\n<ng-template #defaultColumnHeaderTpt let-column=\"column\">\n\t<div class=\"d-flex flex-column\">\n\t\t<h5 class=\"hub-board__column-header-title\">\n\t\t\t{{ column.title }}\n\t\t</h5>\n\t\t<h6 class=\"hub-board__column-header-subtitle\">\n\t\t\t{{ column.description }}\n\t\t</h6>\n\t</div>\n</ng-template>\n\n<!-- Hidden container for rendering drag preview elements -->\n<div #dragPreviewContainer class=\"hub-board__drag-preview-container\" aria-hidden=\"true\"></div>\n", styles: [":root,:host{--hub-board-container-color: var(--hub-sys-text-primary, #212529);--hub-board-container-bg: var(--hub-sys-surface-page, #fff);--hub-board-border-width: var(--hub-ref-border-width, 1px);--hub-board-border-color: var(--hub-sys-border-color-default, #dee2e6);--hub-board-border-radius: var(--hub-ref-radius-md, .375rem);--hub-board-columns-gap: var(--hub-ref-space-3, 1rem);--hub-board-column-width: 256px;--hub-board-column-min-height: 200px;--hub-board-column-body-min-height: 128px;--hub-board-column-body-gap: var(--hub-ref-space-3, 1rem);--hub-board-column-spacer-y: .75rem;--hub-board-column-spacer-x: var(--hub-ref-space-3, 1rem);--hub-board-column-border-width: var(--hub-board-border-width);--hub-board-column-border-color: var(--hub-board-border-color);--hub-board-column-border-radius: var(--hub-board-border-radius);--hub-board-column-box-shadow: none;--hub-board-column-inner-border-radius: var(--hub-board-border-radius);--hub-board-column-cap-padding-y: var(--hub-ref-space-2, .5rem);--hub-board-column-cap-padding-x: var(--hub-ref-space-3, 1rem);--hub-board-column-cap-bg: var(--hub-sys-surface-elevated, #f8f9fa);--hub-board-column-cap-color: inherit;--hub-board-column-header-padding-y: var(--hub-board-column-cap-padding-y);--hub-board-column-header-padding-x: var(--hub-board-column-cap-padding-x);--hub-board-column-header-bg: var(--hub-board-column-cap-bg);--hub-board-column-header-color: var(--hub-board-column-cap-color);--hub-board-column-footer-padding-y: var(--hub-board-column-cap-padding-y);--hub-board-column-footer-padding-x: var(--hub-board-column-cap-padding-x);--hub-board-column-footer-bg: var(--hub-board-column-cap-bg);--hub-board-column-footer-color: var(--hub-board-column-cap-color);--hub-board-column-body-padding-y: var(--hub-board-column-spacer-y);--hub-board-column-body-padding-x: var(--hub-board-column-spacer-x);--hub-board-column-header-title-color: inherit;--hub-board-column-header-title-spacer-y: var(--hub-ref-space-2, .5rem);--hub-board-column-header-subtitle-color: var(--hub-sys-text-muted, #6c757d);--hub-board-column-height: 100%;--hub-board-column-color: var(--hub-board-container-color);--hub-board-column-bg: var(--hub-board-container-bg);--hub-board-card-spacer-y: .75rem;--hub-board-card-spacer-x: var(--hub-ref-space-3, 1rem);--hub-board-card-title-spacer-y: var(--hub-ref-space-2, .5rem);--hub-board-card-title-color: inherit;--hub-board-card-subtitle-color: var(--hub-sys-text-muted, #6c757d);--hub-board-card-border-width: var(--hub-board-border-width);--hub-board-card-border-color: var(--hub-board-border-color);--hub-board-card-border-radius: var(--hub-board-border-radius);--hub-board-card-box-shadow: none;--hub-board-card-inner-border-radius: calc( var(--hub-board-card-border-radius) - var(--hub-board-card-border-width) );--hub-board-card-cap-padding-y: var(--hub-ref-space-2, .5rem);--hub-board-card-cap-padding-x: var(--hub-ref-space-3, 1rem);--hub-board-card-cap-bg: var(--hub-sys-surface-elevated, #f8f9fa);--hub-board-card-cap-color: inherit;--hub-board-card-padding-y: var(--hub-board-card-spacer-y);--hub-board-card-padding-x: var(--hub-board-card-spacer-x);--hub-board-card-header-padding-y: var(--hub-board-card-cap-padding-y);--hub-board-card-header-padding-x: var(--hub-board-card-cap-padding-x);--hub-board-card-header-bg: var(--hub-board-card-cap-bg);--hub-board-card-header-color: var(--hub-board-card-cap-color);--hub-board-card-footer-padding-y: var(--hub-board-card-cap-padding-y);--hub-board-card-footer-padding-x: var(--hub-board-card-cap-padding-x);--hub-board-card-footer-bg: var(--hub-board-card-cap-bg);--hub-board-card-footer-color: var(--hub-board-card-cap-color);--hub-board-card-height: auto;--hub-board-card-color: var(--hub-board-container-color);--hub-board-card-bg: var(--hub-board-container-bg);--hub-board-drag-transition: transform .25s cubic-bezier(0, 0, .2, 1);--hub-board-accent: var(--hub-sys-color-primary, #0d6efd);--hub-board-accent-emphasis: color-mix(in oklch, var(--hub-board-accent) 80%, var(--hub-sys-color-ink, #212529));--hub-board-accent-subtle: color-mix(in oklch, var(--hub-board-accent) 12%, var(--hub-sys-surface-page, #fff));--hub-board-accent-on: oklch(from var(--hub-board-accent) clamp(0, (.62 - l) * 1000, 1) 0 h);--hub-board-placeholder-border-color: var(--hub-board-accent);--hub-board-placeholder-border-width: 2px;--hub-board-placeholder-border-style: dashed;--hub-board-placeholder-bg: var(--hub-board-accent-subtle);--hub-board-placeholder-min-height: 60px}:host{display:block;overflow:auto;width:100%;height:100%}.hub-board__drop-list{height:100%}.hub-board__columns{display:flex;flex-wrap:nowrap;width:100%;height:100%;gap:var(--hub-board-columns-gap, var(--hub-ref-space-3, 1rem))}.hub-board__columns .hub-board__column-container{height:100%}.hub-board__column-container{cursor:grab}.hub-board__column-container--dragging-ghost{opacity:.4;pointer-events:none}.hub-board__column-container--dragging-hide{visibility:hidden;opacity:0;pointer-events:none}.hub-board__column-container--dragging-collapse{display:none}.hub-board__column-container--disabled{cursor:default}.hub-board__column-container[draggable=true]{cursor:grab}.hub-board__column-container[draggable=true]:active{cursor:grabbing}.hub-board__column-container[draggable=false]{cursor:default}.hub-board__column-placeholder{width:var(--hub-board-column-width, 256px);min-width:var(--hub-board-column-width, 256px);height:100%;min-height:var(--hub-board-column-min-height, 200px);border:var(--hub-board-placeholder-border-width, 2px) var(--hub-board-placeholder-border-style, dashed) var(--hub-board-placeholder-border-color, var(--hub-sys-color-primary, #0d6efd));border-radius:var(--hub-board-column-border-radius, var(--hub-ref-radius-md, .375rem));background-color:var(--hub-board-placeholder-bg, var(--hub-sys-color-primary-subtle, rgba(13, 110, 253, .05)));flex-shrink:0;display:flex;align-items:center;justify-content:center;animation:hub-board-placeholder-fade-in .15s ease-out}.hub-board__column{position:relative;display:flex;flex-direction:column;min-width:0;height:var(--hub-board-column-height, 100%);max-height:100%;width:var(--hub-board-column-width, 256px);color:var(--hub-board-container-color, var(--hub-sys-text-primary, #212529));word-wrap:break-word;background-color:var(--hub-board-column-bg, var(--hub-sys-surface-page, #fff));background-clip:border-box;border:var(--hub-board-column-border-width, var(--hub-ref-border-width, 1px)) solid var(--hub-board-column-border-color, var(--hub-sys-border-color-default, #dee2e6));border-radius:var(--hub-board-column-border-radius, var(--hub-ref-radius-md, .375rem));box-shadow:var(--hub-board-column-box-shadow, none)}.hub-board__column-body{flex:1 1 auto;padding:var(--hub-board-column-body-padding-y) var(--hub-board-column-body-padding-x);color:var(--hub-board-column-color, var(--hub-board-container-color));overflow:auto;display:flex;flex-direction:column;gap:var(--hub-board-column-body-gap, var(--hub-ref-space-3, 1rem));min-height:var(--hub-board-column-body-min-height, 128px)}.hub-board__column-body--drag-over{background-color:var(--hub-board-placeholder-bg, var(--hub-sys-color-primary-subtle, rgba(13, 110, 253, .05)))}.hub-board__column-header{padding:var(--hub-board-column-header-padding-y) var(--hub-board-column-header-padding-x);margin:0;color:var(--hub-board-column-header-color);background-color:var(--hub-board-column-header-bg);border-bottom:var(--hub-board-column-border-width, var(--hub-ref-border-width, 1px)) solid var(--hub-board-column-border-color, var(--hub-sys-border-color-default, #dee2e6))}.hub-board__column-header-title{margin-bottom:var(--hub-board-column-header-title-spacer-y, var(--hub-ref-space-2, .5rem));color:var(--hub-board-column-header-title-color, inherit)}.hub-board__column-header-subtitle{margin-top:calc(-.5 * var(--hub-board-column-header-title-spacer-y, var(--hub-ref-space-2, .5rem)));margin-bottom:0;color:var(--hub-board-column-header-subtitle-color, var(--hub-sys-text-muted, #6c757d))}.hub-board__column-header:first-child{border-top-left-radius:var(--hub-board-column-inner-border-radius, var(--hub-board-border-radius, var(--hub-ref-radius-md, .375rem)));border-top-right-radius:var(--hub-board-column-inner-border-radius, var(--hub-board-border-radius, var(--hub-ref-radius-md, .375rem)));border-bottom-right-radius:0;border-bottom-left-radius:0}.hub-board__column-footer{padding:var(--hub-board-column-footer-padding-y) var(--hub-board-column-footer-padding-x);color:var(--hub-board-column-footer-color);background-color:var(--hub-board-column-footer-bg);border-top:var(--hub-board-column-border-width, var(--hub-ref-border-width, 1px)) solid var(--hub-board-column-border-color, var(--hub-sys-border-color-default, #dee2e6))}.hub-board__column-footer:last-child{border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:var(--hub-board-column-inner-border-radius, var(--hub-board-border-radius, var(--hub-ref-radius-md, .375rem)));border-bottom-left-radius:var(--hub-board-column-inner-border-radius, var(--hub-board-border-radius, var(--hub-ref-radius-md, .375rem)))}.hub-board__card{position:relative;display:flex;flex-direction:column;min-width:0;height:var(--hub-board-card-height, auto);color:var(--hub-board-container-color, var(--hub-sys-text-primary, #212529));word-wrap:break-word;background-color:var(--hub-board-card-bg, var(--hub-sys-surface-page, #fff));background-clip:border-box;border:var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px)) solid var(--hub-board-card-border-color, var(--hub-sys-border-color-default, #dee2e6));border-radius:var(--hub-board-card-border-radius, var(--hub-board-border-radius, var(--hub-ref-radius-md, .375rem)));box-shadow:var(--hub-board-card-box-shadow, none);cursor:grab;-webkit-user-select:none;user-select:none;transition:var(--hub-board-drag-transition, transform .25s cubic-bezier(0, 0, .2, 1))}.hub-board__card--dragging-ghost{opacity:.4;pointer-events:none}.hub-board__card--dragging-hide{visibility:hidden;opacity:0;pointer-events:none}.hub-board__card--dragging-collapse{display:none}.hub-board__card--disabled{cursor:default;opacity:.6}.hub-board__card[draggable=true]{cursor:grab}.hub-board__card[draggable=true]:active{cursor:grabbing}.hub-board__card[draggable=false]{cursor:default}.hub-board__card>hr{margin-right:0;margin-left:0}.hub-board__card>.list-group{border-top:inherit;border-bottom:inherit}.hub-board__card>.list-group:first-child{border-top-width:0;border-top-left-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))));border-top-right-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))))}.hub-board__card>.list-group:last-child{border-bottom-width:0;border-bottom-left-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))));border-bottom-right-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))))}.hub-board__card>.hub-board__card-header+.list-group,.hub-board__card>.list-group+.hub-board__card-footer{border-top:0}.hub-board__card-body{flex:1 1 auto;padding:var(--hub-board-card-padding-y) var(--hub-board-card-padding-x);color:var(--hub-board-card-color, var(--hub-board-container-color))}.hub-board__card-title{margin-bottom:var(--hub-board-card-title-spacer-y, var(--hub-ref-space-2, .5rem));color:var(--hub-board-card-title-color, inherit)}.hub-board__card-subtitle{margin-top:calc(-.5 * var(--hub-board-card-title-spacer-y, var(--hub-ref-space-2, .5rem)));margin-bottom:0;color:var(--hub-board-card-subtitle-color, var(--hub-sys-text-muted, #6c757d))}.hub-board__card-header{padding:var(--hub-board-card-header-padding-y) var(--hub-board-card-header-padding-x);color:var(--hub-board-card-header-color);background-color:var(--hub-board-card-header-bg);border-bottom:var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px)) solid var(--hub-board-card-border-color, var(--hub-sys-border-color-default, #dee2e6))}.hub-board__card-header:first-child{border-top-left-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))));border-top-right-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))))}.hub-board__card-footer{padding:var(--hub-board-card-footer-padding-y) var(--hub-board-card-footer-padding-x);color:var(--hub-board-card-footer-color);background-color:var(--hub-board-card-footer-bg);border-top:var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px)) solid var(--hub-board-card-border-color, var(--hub-sys-border-color-default, #dee2e6))}.hub-board__card-footer:last-child{border-bottom-left-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))));border-bottom-right-radius:var(--hub-board-card-inner-border-radius, calc(var(--hub-board-card-border-radius, var(--hub-ref-radius-md, .375rem)) - var(--hub-board-card-border-width, var(--hub-ref-border-width, 1px))))}.hub-board__card-placeholder{min-height:var(--hub-board-placeholder-min-height, 60px);border:var(--hub-board-placeholder-border-width, 2px) var(--hub-board-placeholder-border-style, dashed) var(--hub-board-placeholder-border-color, var(--hub-sys-color-primary, #0d6efd));border-radius:var(--hub-board-card-border-radius, var(--hub-board-border-radius, var(--hub-ref-radius-md, .375rem)));background-color:var(--hub-board-placeholder-bg, var(--hub-sys-color-primary-subtle, rgba(13, 110, 253, .05)));display:flex;align-items:center;justify-content:center;animation:hub-board-placeholder-fade-in .15s ease-out}.hub-board__drag-preview-container{position:fixed;top:-9999px;left:-9999px;pointer-events:none;z-index:-1}@keyframes hub-board-placeholder-fade-in{0%{opacity:0;transform:scaleY(.8)}to{opacity:1;transform:scaleY(1)}}:host([data-variant=primary]),.hub-board[data-variant=primary]{--hub-board-accent: var(--hub-sys-color-primary)}:host([data-variant=secondary]),.hub-board[data-variant=secondary]{--hub-board-accent: var(--hub-sys-color-secondary)}:host([data-variant=success]),.hub-board[data-variant=success]{--hub-board-accent: var(--hub-sys-color-success)}:host([data-variant=danger]),.hub-board[data-variant=danger]{--hub-board-accent: var(--hub-sys-color-danger)}:host([data-variant=warning]),.hub-board[data-variant=warning]{--hub-board-accent: var(--hub-sys-color-warning)}:host([data-variant=info]),.hub-board[data-variant=info]{--hub-board-accent: var(--hub-sys-color-info)}:host([data-variant=neutral]),.hub-board[data-variant=neutral]{--hub-board-accent: var(--hub-sys-color-neutral)}:host([data-variant=light]),.hub-board[data-variant=light]{--hub-board-accent: var(--hub-sys-color-light)}:host([data-variant=dark]),.hub-board[data-variant=dark]{--hub-board-accent: var(--hub-sys-color-dark)}:host([data-variant]),.hub-board[data-variant]{--hub-board-accent-emphasis: color-mix(in oklch, var(--hub-board-accent) 80%, var(--hub-sys-color-ink, #212529));--hub-board-accent-subtle: color-mix(in oklch, var(--hub-board-accent) 12%, var(--hub-sys-surface-page, #fff));--hub-board-accent-on: oklch(from var(--hub-board-accent) clamp(0, (.62 - l) * 1000, 1) 0 h)}\n"] }]
        }], propDecorators: { board: [{ type: i0.Input, args: [{ isSignal: true, alias: "board", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], columnSortingDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnSortingDisabled", required: false }] }], dragBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragBehavior", required: false }] }], cardTpt: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CardTemplateDirective), { ...{
                            read: (TemplateRef)
                        }, isSignal: true }] }], columnHeaderTpt: [{ type: i0.ContentChild, args: [i0.forwardRef(() => BoardColumnHeaderDirective), { ...{
                            read: (TemplateRef)
                        }, isSignal: true }] }], columnFooterTpt: [{ type: i0.ContentChild, args: [i0.forwardRef(() => BoardColumnFooterDirective), { ...{
                            read: (TemplateRef)
                        }, isSignal: true }] }], cardPlaceholderTpt: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CardPlaceholderDirective), { ...{
                            read: (TemplateRef)
                        }, isSignal: true }] }], columnPlaceholderTpt: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ColumnPlaceholderDirective), { ...{
                            read: (TemplateRef)
                        }, isSignal: true }] }], cardDragPreviewTpt: [{ type: i0.ContentChild, args: [i0.forwardRef(() => CardDragPreviewDirective), { ...{
                            read: (TemplateRef)
                        }, isSignal: true }] }], columnDragPreviewTpt: [{ type: i0.ContentChild, args: [i0.forwardRef(() => ColumnDragPreviewDirective), { ...{
                            read: (TemplateRef)
                        }, isSignal: true }] }], dragPreviewContainer: [{ type: i0.ViewChild, args: ['dragPreviewContainer', { isSignal: true }] }], onCardClick: [{ type: i0.Output, args: ["onCardClick"] }], onCardMoved: [{ type: i0.Output, args: ["onCardMoved"] }], onColumnMoved: [{ type: i0.Output, args: ["onColumnMoved"] }], reachedEnd: [{ type: i0.Output, args: ["reachedEnd"] }] } });

/**
 * Angular module that provides board functionality with drag-and-drop support.
 *
 * This module includes all the necessary components and directives for creating
 * Kanban-style boards with customizable columns, cards, and templates.
 *
 * @deprecated Use standalone components instead. Import individual components and directives directly.
 * @publicApi
 *
 * @example
 * ```typescript
 * // Legacy module approach (not recommended)
 * import { BoardModule } from 'ng-hub-ui-board';
 *
 * @NgModule({
 *   imports: [BoardModule]
 * })
 * export class AppModule {}
 *
 * // Recommended standalone approach
 * import { HubBoardComponent, CardTemplateDirective } from 'ng-hub-ui-board';
 *
 * @Component({
 *   standalone: true,
 *   imports: [HubBoardComponent, CardTemplateDirective]
 * })
 * export class MyComponent {}
 * ```
 */
class BoardModule {
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BoardModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
    static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.1", ngImport: i0, type: BoardModule, imports: [HubBoardComponent,
            CardTemplateDirective,
            BoardColumnHeaderDirective,
            BoardColumnFooterDirective,
            CardPlaceholderDirective,
            ColumnPlaceholderDirective], exports: [HubBoardComponent,
            CardTemplateDirective,
            BoardColumnHeaderDirective,
            BoardColumnFooterDirective,
            CardPlaceholderDirective,
            ColumnPlaceholderDirective] });
    static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BoardModule });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: BoardModule, decorators: [{
            type: NgModule,
            args: [{
                    declarations: [],
                    imports: [
                        HubBoardComponent,
                        CardTemplateDirective,
                        BoardColumnHeaderDirective,
                        BoardColumnFooterDirective,
                        CardPlaceholderDirective,
                        ColumnPlaceholderDirective
                    ],
                    exports: [
                        HubBoardComponent,
                        CardTemplateDirective,
                        BoardColumnHeaderDirective,
                        BoardColumnFooterDirective,
                        CardPlaceholderDirective,
                        ColumnPlaceholderDirective
                    ]
                }]
        }] });

/**
 * Converts a hexadecimal color string into its inverted counterpart, offering both
 * high-contrast black/white and full-spectrum inversion modes.
 *
 * @publicApi
 */
class InvertColorPipe {
    /**
     * Inverts a HEX color value.
     *
     * @param hex - Color expressed as a 3- or 6-digit HEX string with or without a hash prefix.
     * @param bw - When `true`, returns either black or white based on perceived brightness to maximise contrast.
     * @returns The inverted color represented as a 6-digit HEX string (always prefixed with `#`).
     * @throws Error if the provided value cannot be parsed as a valid HEX color.
     */
    transform(hex, bw) {
        if (!hex) {
            return '#000000';
        }
        let normalizedHex = hex;
        if (normalizedHex.indexOf('#') === 0) {
            normalizedHex = normalizedHex.slice(1);
        }
        if (normalizedHex.length === 3) {
            // Convert shorthand notation (e.g., #abc) to full length (#aabbcc)
            normalizedHex =
                normalizedHex[0] +
                    normalizedHex[0] +
                    normalizedHex[1] +
                    normalizedHex[1] +
                    normalizedHex[2] +
                    normalizedHex[2];
        }
        if (normalizedHex.length !== 6) {
            throw new Error('Invalid HEX color.');
        }
        const r = parseInt(normalizedHex.slice(0, 2), 16);
        const g = parseInt(normalizedHex.slice(2, 4), 16);
        const b = parseInt(normalizedHex.slice(4, 6), 16);
        if (bw) {
            // http://stackoverflow.com/a/3943023/112731
            return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? '#000000' : '#FFFFFF';
        }
        const invertedR = (255 - r).toString(16).padStart(2, '0');
        const invertedG = (255 - g).toString(16).padStart(2, '0');
        const invertedB = (255 - b).toString(16).padStart(2, '0');
        return `#${invertedR}${invertedG}${invertedB}`;
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: InvertColorPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
    static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "22.0.1", ngImport: i0, type: InvertColorPipe, isStandalone: true, name: "invertColor" });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: InvertColorPipe, decorators: [{
            type: Pipe,
            args: [{
                    name: 'invertColor',
                    standalone: true
                }]
        }] });

// The array move/transfer helpers used when reordering cards and columns now live in the
// shared `ng-hub-ui-utils` drag-and-drop core and are re-exported from `./array-helpers`
// (kept in a dedicated file so the external re-export does not sit alongside the inline
// `import('./board-column')` type references above, which trips ng-packagr's compiler).

/*
 * Public API Surface of board
 */
// module

/**
 * Generated bundle index. Do not edit.
 */

export { BoardColumnFooterDirective, BoardColumnHeaderDirective, BoardModule, CardDragPreviewDirective, CardPlaceholderDirective, CardTemplateDirective, ColumnDragPreviewDirective, ColumnPlaceholderDirective, HubBoardComponent, InvertColorPipe };
//# sourceMappingURL=ng-hub-ui-board.mjs.map