{"version":3,"file":"KanbanColumn.mjs","names":[],"sources":["../../../../package/common/kanbanBoard/KanbanColumn.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode, Ref, UIEvent } from 'react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { useDroppable } from '@dnd-kit/core';\nimport { SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';\nimport { CSS } from '@dnd-kit/utilities';\nimport cx from 'classnames';\n\nimport type { DataProps, SlotClassNames, SlotStyles } from '../types/slots.types';\nimport type { KanbanColumnProps, KanbanColumnSlots } from './kanbanBoard.types';\nimport styles from './kanbanBoard.module.pcss';\nimport KanbanItem from './KanbanItem';\nimport Loader from '../loader';\n\n// Hoisted so these are referentially stable across renders — inline literals here would give\n// `useSortable` a new `data`/`disabled` object every render (every column re-renders on every\n// `dragOver` while a drag is in progress, via the board's `placement`), which can spin dnd-kit's own\n// registration effect into a render loop that eventually trips React's \"Maximum update depth\n// exceeded\" guard. Same fix as the sensor options in `KanbanBoard.tsx`.\nconst COLUMN_SORTABLE_DATA = { type: 'column' };\nconst COLUMN_SORTABLE_DISABLED = { draggable: true, droppable: true };\n\ninterface KanbanColumnInternalProps<T> extends KanbanColumnProps {\n  itemIds: string[];\n  items: Record<string, T>;\n  renderItem: (item: T, id: string) => ReactNode;\n  dragHandle?: boolean;\n  /** Shrink to a narrow strip. The board decides when, see `KanbanBoardProps.collapseEmptyColumns`. */\n  collapsed?: boolean;\n  /** Work-in-progress limit — see `KanbanColumnDef.maxItems`. Display-only. */\n  maxItems?: number;\n  /** Freezes the column — see `KanbanColumnDef.disabled`. */\n  disabled?: boolean;\n  /** Returns whether a specific card can't be picked up — see `KanbanBoardProps.getItemDisabled`. */\n  getItemDisabled?: (item: T, id: string) => boolean;\n  /** Names a card's drag handle — see `KanbanBoardProps.getItemLabel`. */\n  getItemLabel?: (item: T, id: string) => string;\n  /** Custom content shown when the column has no cards — see `KanbanBoardProps.renderEmptyColumn`. */\n  emptyPlaceholder?: ReactNode;\n  /** Custom header actions — see `KanbanBoardProps.renderColumnActions`. */\n  actions?: ReactNode;\n  /** Allow the whole column to be dragged (by its header) to reorder it — see `KanbanBoardProps.reorderableColumns`. */\n  reorderable?: boolean;\n  /**\n   * Whether the card currently being dragged (if any) is actually a column. Suppresses the\n   * cards drop-target highlight, which otherwise lights up on every column a dragged column\n   * passes over — that highlight means \"drop a card here\", which doesn't apply mid-column-drag.\n   */\n  activeIsColumn?: boolean;\n  /** Bounds the column's height and makes its card list scroll — see `KanbanBoardProps.columnMaxHeight`. */\n  columnMaxHeight?: string;\n  /** Whether more items exist for this column — see `KanbanColumnDef.hasMore`. */\n  hasMore?: boolean;\n  /** Requests the next page for this column — see `KanbanBoardProps.onLoadMore`. */\n  onLoadMore?: () => void | Promise<void>;\n  /** Scroll distance from the bottom that triggers `onLoadMore` — see `KanbanBoardProps.loadMoreThreshold`. */\n  loadMoreThreshold?: number;\n}\n\ninterface ColumnHeaderContentProps {\n  title: string;\n  count: number;\n  maxItems?: number;\n  actions?: ReactNode;\n  /** Makes the title the column's keyboard drag handle. */\n  titleProps?: HTMLAttributes<HTMLSpanElement> & DataProps & { ref?: Ref<HTMLSpanElement> };\n  classNames?: SlotClassNames<KanbanColumnSlots>;\n  styles?: SlotStyles<KanbanColumnSlots>;\n}\n\n// Also rendered by the drag overlay: with the title alone, the overlay was shorter than the header\n// it covers, and the faded original showed below it.\nexport function ColumnHeaderContent({\n  title,\n  count,\n  maxItems,\n  actions,\n  titleProps,\n  classNames,\n  styles: slotStyles,\n}: ColumnHeaderContentProps) {\n  const isOverLimit = maxItems != null && count > maxItems;\n  return (\n    <>\n      <span {...titleProps} className={styles.columnHeaderTitle}>\n        {title}\n      </span>\n      <div className={styles.columnHeaderTrailing}>\n        {maxItems != null && (\n          <span\n            className={cx(styles.columnHeaderCount, isOverLimit && styles.columnHeaderCountOver, classNames?.count)}\n            style={slotStyles?.count}\n          >\n            {count}/{maxItems}\n          </span>\n        )}\n        {actions != null && (\n          <div className={cx(styles.columnHeaderActions, classNames?.actions)} style={slotStyles?.actions}>\n            {actions}\n          </div>\n        )}\n      </div>\n    </>\n  );\n}\n\nfunction KanbanColumn<T>({\n  id,\n  title,\n  itemIds,\n  items,\n  renderItem,\n  dragHandle,\n  collapsed,\n  maxItems,\n  disabled,\n  getItemDisabled,\n  getItemLabel,\n  emptyPlaceholder,\n  actions,\n  reorderable,\n  activeIsColumn,\n  columnMaxHeight,\n  hasMore,\n  onLoadMore,\n  loadMoreThreshold = 100,\n  className,\n  style,\n  classNames,\n  styles: slotStyles,\n  ...rest\n}: KanbanColumnInternalProps<T>) {\n  const droppableId = `column-${id}`;\n  const { setNodeRef, isOver } = useDroppable({ id: droppableId, disabled });\n  const isOverLimit = maxItems != null && itemIds.length > maxItems;\n\n  const itemsRef = useRef<HTMLDivElement | null>(null);\n  const setItemsRef = useCallback(\n    (node: HTMLDivElement | null) => {\n      itemsRef.current = node;\n      setNodeRef(node);\n    },\n    [setNodeRef],\n  );\n  const isLoadingMoreRef = useRef(false);\n  const [isLoadingMore, setIsLoadingMore] = useState(false);\n\n  const triggerLoadMore = useCallback(() => {\n    if (!onLoadMore || isLoadingMoreRef.current) return;\n    isLoadingMoreRef.current = true;\n    setIsLoadingMore(true);\n    Promise.resolve(onLoadMore()).finally(() => {\n      isLoadingMoreRef.current = false;\n      setIsLoadingMore(false);\n    });\n  }, [onLoadMore]);\n\n  const handleItemsScroll = useCallback(\n    (event: UIEvent<HTMLDivElement>) => {\n      if (!hasMore) return;\n      const el = event.currentTarget;\n      if (el.scrollTop + el.clientHeight >= el.scrollHeight - loadMoreThreshold) triggerLoadMore();\n    },\n    [hasMore, loadMoreThreshold, triggerLoadMore],\n  );\n\n  // The scroll trigger above can only fire once the list actually overflows and scrolls. A batch\n  // smaller than the column's own visible height never generates a scroll event, which would\n  // otherwise dead-end pagination after the first page. Re-probes at most once per (item count,\n  // visible height) pair, so settled content can't re-ask a source that already ran dry.\n  //\n  // It asks while the cards leave empty space below them, not while the list fails to overflow:\n  // on a board that grows with its cards no list ever overflows, and that rule loaded every page.\n  // This one fills a short column up to the tallest and stops.\n  const fillProbeRef = useRef('');\n  useEffect(() => {\n    if (!hasMore || isLoadingMore) return;\n    const el = itemsRef.current;\n    if (!el || el.clientHeight === 0) return;\n    const last = el.lastElementChild as HTMLElement | null;\n    const paddingBottom = parseFloat(getComputedStyle(el).paddingBottom) || 0;\n    if ((last ? last.offsetTop + last.offsetHeight : 0) + paddingBottom >= el.clientHeight - 1) return;\n    const probe = `${itemIds.length}:${el.clientHeight}`;\n    if (fillProbeRef.current === probe) return;\n    fillProbeRef.current = probe;\n    triggerLoadMore();\n  }, [hasMore, isLoadingMore, itemIds.length, triggerLoadMore]);\n\n  const {\n    attributes: columnAttributes,\n    listeners: columnListeners,\n    setNodeRef: setColumnNodeRef,\n    setActivatorNodeRef: setColumnActivatorRef,\n    transform: columnTransform,\n    transition: columnTransition,\n    isDragging: isColumnDragging,\n    isOver: isColumnOver,\n  } = useSortable({\n    id,\n    data: COLUMN_SORTABLE_DATA,\n    disabled: reorderable ? false : COLUMN_SORTABLE_DISABLED,\n  });\n\n  return (\n    <div\n      {...rest}\n      ref={setColumnNodeRef}\n      id={id}\n      data-kanban-column=''\n      className={cx(\n        styles.column,\n        isOver && !activeIsColumn && styles.columnOver,\n        isColumnOver && styles.columnDropTarget,\n        collapsed && styles.columnCollapsed,\n        isOverLimit && styles.columnOverLimit,\n        disabled && styles.columnDisabled,\n        isColumnDragging && styles.columnDragging,\n        className,\n      )}\n      style={{ transform: CSS.Transform.toString(columnTransform), transition: columnTransition, ...style }}\n    >\n      {/* The whole header is the pointer grab area, but only the title is the keyboard handle: a\n          focusable header was a button around the actions' own buttons, and the Enter meant for\n          one of them picked the column up instead. */}\n      <div\n        className={cx(styles.columnHeader, reorderable && styles.columnHeaderReorderable, classNames?.header)}\n        style={slotStyles?.header}\n        {...(reorderable ? columnListeners : undefined)}\n      >\n        <ColumnHeaderContent\n          title={title}\n          count={itemIds.length}\n          maxItems={maxItems}\n          actions={actions}\n          titleProps={\n            reorderable ? { ...columnAttributes, ref: setColumnActivatorRef, 'data-kanban-handle': '' } : undefined\n          }\n          classNames={classNames}\n          styles={slotStyles}\n        />\n      </div>\n      <SortableContext id={id} items={itemIds} strategy={verticalListSortingStrategy}>\n        <div\n          ref={setItemsRef}\n          className={cx(styles.columnItems, classNames?.items)}\n          style={{ maxHeight: columnMaxHeight, ...slotStyles?.items }}\n          onScroll={handleItemsScroll}\n        >\n          {itemIds.length === 0 && emptyPlaceholder != null ? (\n            <div className={cx(styles.columnPlaceholder, classNames?.placeholder)} style={slotStyles?.placeholder}>\n              {emptyPlaceholder}\n            </div>\n          ) : null}\n          {itemIds.map((itemId) => (\n            <KanbanItem\n              key={itemId}\n              id={itemId}\n              label={items[itemId] !== undefined ? getItemLabel?.(items[itemId], itemId) : undefined}\n              dragHandle={dragHandle}\n              disabled={\n                disabled || (items[itemId] !== undefined && (getItemDisabled?.(items[itemId], itemId) ?? false))\n              }\n              preventAsDropTarget={disabled}\n            >\n              {items[itemId] !== undefined ? renderItem(items[itemId], itemId) : null}\n            </KanbanItem>\n          ))}\n          {isLoadingMore && (\n            <div\n              className={cx(styles.columnLoadingIndicator, classNames?.loadingIndicator)}\n              style={slotStyles?.loadingIndicator}\n            >\n              <Loader size='2.6em' />\n            </div>\n          )}\n        </div>\n      </SortableContext>\n    </div>\n  );\n}\n\nexport default KanbanColumn;\n"],"mappings":"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"}