@brickclay-org/ui 0.1.93 → 0.1.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -4899,81 +4899,117 @@ declare const BK_TABLE: readonly [typeof BkTable, typeof BkTableTitle, typeof Bk
4899
4899
  /**
4900
4900
  * Types for bk-kanban.
4901
4901
  *
4902
- * The board is content-agnostic by design (see the component's own doc
4903
- * comment): it only ever moves opaque `T` values between columns and reports
4904
- * what happened. Every type here describes either a column, or one of the
4905
- * events the board reports never anything about what a card renders as.
4902
+ * Columns own their items a single nested tree is the source of truth:
4903
+ * `columns: KanbanColumn<T>[]`, each carrying its own `items: T[]`. There is
4904
+ * no separate flat cards array anywhere; grouping, ordering, and lookup all
4905
+ * fall out of the tree itself.
4906
+ *
4907
+ * Every drag, add, and remove operation emits only the column(s) actually
4908
+ * affected — never the whole board — each with its complete, current
4909
+ * `items` (column *reordering* is the one exception — see
4910
+ * `BkKanbanColumnReorderPayload`). There's deliberately no
4911
+ * `itemsMovedWithin` / `itemsMovedBetween` pair of "what changed" fields:
4912
+ * the consumer already gets the complete, ordered `items` for every affected
4913
+ * column, so diffing that against whatever it had before tells it exactly
4914
+ * what moved and where without the board also maintaining a second,
4915
+ * redundant description of the same fact.
4916
+ */
4917
+ /**
4918
+ * Minimum shape every item must satisfy. `label` is the one content field
4919
+ * the board itself ever reads (for the built-in add/remove affordances'
4920
+ * aria-labels, and as the default new-item label) — everything past the
4921
+ * four required fields is entirely up to the consumer via the index
4922
+ * signature (`type`, `priority`, `assignee`, `dueDate`, whatever a real card
4923
+ * needs).
4906
4924
  */
4907
- /** One column/status definition. Any number of columns — no fixed status set
4908
- * assumed; a workflow with two lanes and one with fifteen both just pass a
4909
- * `columns` array of the size they need. */
4910
- interface BkKanbanColumn {
4925
+ interface ColumnItem {
4911
4926
  id: string;
4912
4927
  label: string;
4913
- /** Cap on cards in this column. Omit for no limit — see `wipLimitBehavior`. */
4914
- wipLimit?: number;
4915
- /**
4916
- * Extra class(es) appended to this column's header bar — the row holding
4917
- * the label, the count badge, and `columnHeaderActionsTemplate`, if any.
4918
- * Named `colHeaderClass` (not `headerClass`) so it reads unambiguously as
4919
- * "this column's header" once a column is nested inside `bk-kanban`. Same
4920
- * idea as `panelClass` on `bk-dialog`/`bk-popover`, or `headerClass` on
4921
- * `bk-grid`'s `TableColumn`: it only adds to the header's own classes,
4922
- * bk-kanban's own layout still stands, and nothing stops you passing
4923
- * whatever you'd pass any other element — a single class, several, a
4924
- * whole Tailwind utility stack.
4925
- *
4926
- * For properties the header doesn't already declare (a `border`,
4927
- * `box-shadow`, rounded corners) a plain class works exactly as you'd
4928
- * expect. For its **background** specifically, set the
4929
- * `--bk-kanban-header-bg` custom property instead of a `bg-*` utility
4930
- * Angular injects this component's own stylesheet after the page's
4931
- * global one, so a same-specificity utility class can never outrank the
4932
- * header's own background declaration; the custom property exists
4933
- * precisely so `colHeaderClass` still has a reliable way to change it.
4934
- */
4928
+ sortOrder: number;
4929
+ /** The id of the column that currently owns this item. Kept in sync by
4930
+ * the board on every move — never write this yourself. */
4931
+ parentId: string;
4932
+ [key: string]: unknown;
4933
+ }
4934
+ /** One column, owning its own items the array the board mutates in place
4935
+ * on every drag/add/remove, the same way the old flat `cards` array used to
4936
+ * be mutated (see the component's own doc comment). */
4937
+ interface KanbanColumn<T extends ColumnItem = ColumnItem> {
4938
+ id: string;
4939
+ label: string;
4940
+ /** Cap on items in this column. Omit for no limit — see `wipLimitBehavior`. */
4941
+ limit?: number;
4942
+ sortOrder: number;
4943
+ items: T[];
4944
+ /** Whether this column accepts a new item gates the built-in "+" button
4945
+ * (alongside the board-wide `showAddItem`) *and* `addItem()`/the
4946
+ * `addItem` template context function itself, so a column marked
4947
+ * `allowAdd: false` can't be added to through any path, built-in or
4948
+ * custom. Omit (or `true`) to allow see §14. */
4949
+ allowAdd?: boolean;
4950
+ /** Whether an item can be removed from this column — same gating as
4951
+ * `allowAdd`, covering the built-in remove button, `removeItem()`, and
4952
+ * `BkKanbanCardContext.removeItem`. Omit (or `true`) to allow. */
4953
+ allowRemove?: boolean;
4954
+ /** Per-column override of the board-wide `emptyMessage`, shown while
4955
+ * `items` is empty. Ignored once `emptyStateTemplate` is supplied — a
4956
+ * template is a bigger override than swapping in different words. */
4957
+ emptyMessage?: string;
4958
+ /** Extra class(es) appended to this column's header bar. Same idea as
4959
+ * `panelClass` on `bk-dialog`/`bk-popover` — it only adds to the header's
4960
+ * own classes, and reliably wins over the header's own default styling
4961
+ * for any property, background included (the component's base rules are
4962
+ * written with `:where(...)` in kanban.css specifically so a plain
4963
+ * utility class here always outranks them, regardless of stylesheet load
4964
+ * order). `--bk-kanban-header-bg` is also there as an alternative way to
4965
+ * set just the background, e.g. from an inline style. */
4935
4966
  colHeaderClass?: string;
4936
- /**
4937
- * Extra class(es) appended to this column's own wrapper — the element
4938
- * `wipLimit` feedback (`bk-kanban-column-warn`/`bk-kanban-column-live-over`)
4939
- * already lands on, and the ancestor `colHeaderClass` reaches the header
4940
- * through. Same idea as `colHeaderClass` one level up: it only adds to the
4941
- * wrapper's own classes, nothing here replaces bk-kanban's own layout.
4942
- *
4943
- * Same background caveat as `colHeaderClass` too — set the
4944
- * `--bk-kanban-column-bg` custom property rather than a `bg-*` utility to
4945
- * change the wrapper's background reliably; see `colHeaderClass`'s doc
4946
- * comment for why.
4947
- */
4967
+ /** Extra class(es) appended to this column's own wrapper. Same reasoning
4968
+ * as `colHeaderClass` reliably wins for any property; `--bk-kanban-column-bg`
4969
+ * is the equivalent background alternative here. */
4948
4970
  columnClass?: string;
4949
- /**
4950
- * Placeholder text shown in this column's body when it holds no cards,
4951
- * overriding the board-wide `noDataText` input for this column alone.
4952
- * Ignored once `emptyStateTemplate` is supplied — a template is a bigger
4953
- * override than swapping in different words, so it wins outright rather
4954
- * than being handed a piece of text it may not even render.
4955
- */
4956
- noDataText?: string;
4971
+ /** Cross-column move, source side: the id of the item that just left. */
4972
+ sentChildId?: string;
4973
+ /** Cross-column move, destination side: the id of the item that just arrived. */
4974
+ receivedChildId?: string;
4975
+ /** A just-added item's id. */
4976
+ newAddedItemId?: string;
4977
+ /** Set alongside `newAddedItemId`, always `true` — never present on a
4978
+ * column for any other kind of change. Exists so a consumer can branch on
4979
+ * "did creation succeed" without having to check `newAddedItemId` for
4980
+ * both existence *and* the right meaning; see §10/§11. */
4981
+ newItemCreated?: boolean;
4982
+ /** A just-removed item's id. */
4983
+ removedItemId?: string;
4957
4984
  }
4958
4985
  /**
4959
- * How far a drag is allowed to travel.
4986
+ * How far a card drag is allowed to travel — the single control for both
4987
+ * "is dragging on at all" and "how far", so there's nothing to keep in sync
4988
+ * between two separate inputs.
4960
4989
  *
4961
- * `'same'` only reorders within a column (manual prioritization on a board
4962
- * whose statuses shouldn't change from a drag). `'cross'` only moves cards
4963
- * between columns, with no in-column reordering. `'both'` (the default)
4964
- * allows either.
4990
+ * `'within'` only reorders inside a column (manual prioritization on
4991
+ * a board whose statuses shouldn't change from a drag). `'between'`
4992
+ * only moves items across columns, with no in-column reordering. `'both'`
4993
+ * (the default) allows either. `'none'` turns card dragging off entirely —
4994
+ * every card's own `cdkDrag` and every column body's own `cdkDropList` are
4995
+ * disabled, the same as the old `dragEnabled: false` used to do. Unrelated
4996
+ * to `dragColumn`, which is its own separate switch for dragging
4997
+ * *columns* themselves.
4965
4998
  */
4966
- type BkKanbanDragScope = 'same' | 'cross' | 'both';
4999
+ type BkKanbanMovementRestriction = 'within' | 'between' | 'both' | 'none';
4967
5000
  /**
4968
- * What happens when a drop would push a column's card count past its
4969
- * `wipLimit`.
5001
+ * What happens when a drop or an add would push a column's item count past
5002
+ * its `limit`.
4970
5003
  *
4971
- * `'block'` rejects the drop — nothing is mutated, so the card returns to
4972
- * its origin the same way any cancelled CDK drag does. `'warn'` lets the
4973
- * drop through but flags the column. `'none'` ignores limits at drop time
4974
- * entirely (they still show in the header, purely as information).
4975
- * `columnLimitExceeded` fires whenever a drop *would* exceed the limit,
4976
- * regardless of which of the three is active.
5004
+ * `'block'` rejects the operation — nothing is mutated (a drop returns the
5005
+ * item to its origin the same way any cancelled CDK drag does; an add via
5006
+ * `addItem` simply does nothing). `'warn'` lets it through but flags the
5007
+ * column. `'none'` ignores limits at drop/add time entirely (still shown in
5008
+ * the header, purely as information). `columnLimitExceeded` fires whenever
5009
+ * an operation *would* exceed the limit, regardless of which of the three is
5010
+ * active. Only ever applies to adding or a cross-column move — reordering
5011
+ * within a column never changes that column's count, so it's never checked
5012
+ * against the limit.
4977
5013
  */
4978
5014
  type BkKanbanWipLimitBehavior = 'block' | 'warn' | 'none';
4979
5015
  /**
@@ -4981,289 +5017,471 @@ type BkKanbanWipLimitBehavior = 'block' | 'warn' | 'none';
4981
5017
  *
4982
5018
  * `'live'` (default) highlights the hovered column the moment it would go
4983
5019
  * over, while the drag is still in progress. `'on-drop'` says nothing until
4984
- * release, when `wipLimitBehavior` takes over. See the component doc for why
4985
- * `'live'` is the recommended default.
5020
+ * release, when `wipLimitBehavior` takes over.
4986
5021
  */
4987
5022
  type BkKanbanDragPreviewMode = 'live' | 'on-drop';
4988
5023
  /**
4989
5024
  * Where *vertical* scrolling lives once content outgrows the board's given
4990
- * height (see the doc comment on `.bk-kanban` in `kanban.css` for how that
4991
- * height is resolved from the host in the first place). Horizontal
4992
- * scrolling — across columns — always lives on the board itself
4993
- * (`.bk-kanban`'s own `overflow-x: auto`) regardless of this setting; only
4994
- * the vertical axis moves.
5025
+ * height. Horizontal scrolling across columns always lives on the board
5026
+ * itself regardless of this setting; only the vertical axis moves.
4995
5027
  *
4996
- * `'body'` (default): each column is stretched to the board's height and
4997
- * scrolls *independently* — its own header stays fixed, only
4998
- * `.bk-kanban-column-body` (the card list beneath it) scrolls, so a long
4999
- * column never pushes its own header out of view, and every column can be
5000
- * at a different scroll position at once.
5028
+ * `'column'` (default): each column is stretched to the board's height and
5029
+ * scrolls *independently* — its own header stays fixed, only the card list
5030
+ * beneath it scrolls, so a long column never pushes its own header out of
5031
+ * view, and every column can be at a different scroll position at once.
5001
5032
  *
5002
- * `'wrapper'`: the board itself — `.bk-kanban`, the element containing every
5003
- * column — is what scrolls vertically, all columns together as a single
5004
- * region (headers included). Columns fall back to their own natural height
5005
- * here rather than being stretched, since nothing needs bounding
5006
- * individually any more once the board is the one doing the scrolling.
5033
+ * `'wrapper'`: the board itself — the element containing every column — is
5034
+ * what scrolls vertically, all columns together as a single region (headers
5035
+ * included). Columns fall back to their own natural height here rather than
5036
+ * being stretched, since nothing needs bounding individually any more once
5037
+ * the board is the one doing the scrolling.
5007
5038
  */
5008
- type BkKanbanColumnScrollMode = 'body' | 'wrapper';
5009
- /** Emitted once a drag-and-drop completes — a same-column reorder or a
5010
- * cross-column move. `fromColumnId === toColumnId` for the former. */
5011
- interface BkKanbanCardMovedEvent<T = any> {
5012
- card: T;
5013
- fromColumnId: string;
5014
- toColumnId: string;
5015
- newIndex: number;
5039
+ type BkKanbanColumnScrollMode = 'column' | 'wrapper';
5040
+ /**
5041
+ * How much a column-reorder `columnsChanged` emission carries, per column —
5042
+ * see `BkKanbanColumnSummary`. Only ever affects a *column* reorder; every
5043
+ * other operation (item reorder, cross-column move, add, remove) always
5044
+ * emits complete `KanbanColumn<T>` entries regardless of this setting.
5045
+ *
5046
+ * `'summary'` (lighter): just `{ id, sortOrder }` for every column in the
5047
+ * board, in its resulting order — enough for a consumer that only persists
5048
+ * column ordering and doesn't need each column's `items` echoed back.
5049
+ *
5050
+ * `'complete'` (default): the full `KanbanColumn<T>` for every column,
5051
+ * `items` included — the same shape every other operation already emits.
5052
+ */
5053
+ type BkKanbanColumnReorderPayload = 'summary' | 'complete';
5054
+ /** The lightweight per-column entry a `'summary'`-mode column reorder emits
5055
+ * — see `BkKanbanColumnReorderPayload`. */
5056
+ interface BkKanbanColumnSummary {
5057
+ id: string;
5058
+ sortOrder: number;
5016
5059
  }
5017
- /** Emitted once a column reorder completes — see `columnDragEnabled`. Unlike
5018
- * `cardMoved`, there's only one axis to report: columns don't nest inside
5019
- * one another, so a move is always "this column, from this index, to this
5020
- * one" rather than needing a from/to pair of containers. */
5021
- interface BkKanbanColumnMovedEvent {
5060
+ /**
5061
+ * Emitted after any operation that changes the board a column reorder, an
5062
+ * item reorder within a column, a cross-column move, an add, or a remove.
5063
+ * Carries only the column(s) actually affected, each reflecting state
5064
+ * *after* the operation succeeded.
5065
+ *
5066
+ * Which change-metadata field is set on a given entry tells you which
5067
+ * operation just happened:
5068
+ *
5069
+ * - a `BkKanbanColumnSummary` entry (just `id` + `sortOrder`, no `items`) →
5070
+ * a column reorder with `columnReorderPayload: 'summary'`; every column in
5071
+ * the board is present, not just the ones that moved.
5072
+ * - a complete `KanbanColumn<T>` with no change-metadata field set → a
5073
+ * column reorder with `columnReorderPayload: 'complete'` (the default);
5074
+ * again every column in the board is present.
5075
+ * - `newAddedItemId` + `newItemCreated: true` set → an item was added to
5076
+ * this column (one entry).
5077
+ * - `removedItemId` set → an item was removed from this column (one entry).
5078
+ * - `sentChildId` **or** `receivedChildId` set → a cross-column move
5079
+ * (`columns` has exactly two entries, source and destination).
5080
+ * - none of the above, one entry → an item was reordered inside that column;
5081
+ * compare its `items` against what you had before to see what moved.
5082
+ */
5083
+ interface BkKanbanColumnsChangedEvent<T extends ColumnItem = ColumnItem> {
5084
+ columns: Array<KanbanColumn<T> | BkKanbanColumnSummary>;
5085
+ }
5086
+ /** Emitted when a drop or an add would put a column over its `limit` — see
5087
+ * `BkKanbanWipLimitBehavior` for when the operation itself still succeeds. */
5088
+ interface BkKanbanColumnLimitExceededEvent {
5022
5089
  columnId: string;
5023
- fromIndex: number;
5024
- toIndex: number;
5090
+ limit: number;
5091
+ attemptedCount: number;
5025
5092
  }
5026
5093
  /**
5027
- * Emitted when a card (or a region inside it) is clicked.
5094
+ * Emitted when an item (or a region inside it) is clicked.
5028
5095
  *
5029
- * `target` is `'card'` for a click anywhere on the card that isn't a more
5096
+ * `target` is `'card'` for a click anywhere on the item that isn't a more
5030
5097
  * specific region, or whatever name was given to a
5031
5098
  * `[bkKanbanClickTarget]="'…'"` element the click landed inside.
5032
5099
  */
5033
- interface BkKanbanCardClickedEvent<T = any> {
5034
- card: T;
5100
+ interface BkKanbanItemClickedEvent<T extends ColumnItem = ColumnItem> {
5101
+ item: T;
5035
5102
  target: string;
5036
5103
  }
5037
- /** Emitted when a drop would put a column over its `wipLimit` — see
5038
- * `BkKanbanWipLimitBehavior` for when the drop itself still succeeds. */
5039
- interface BkKanbanColumnLimitExceededEvent {
5040
- columnId: string;
5041
- wipLimit: number;
5042
- attemptedCount: number;
5043
- }
5044
5104
  /** Reserved: see `columnActionTriggered` on `BkKanban` and
5045
5105
  * `BkKanbanColumnActionsContext.trigger`. */
5046
5106
  interface BkKanbanColumnActionTriggeredEvent {
5047
5107
  columnId: string;
5048
5108
  }
5109
+ /** What a consumer hands back to `BkKanbanItemMoveBetweenColumnsEvent.resolve`
5110
+ * — see that event's doc comment and §5-§9/§23 of the spec this shipped
5111
+ * against. */
5112
+ interface BkKanbanItemMoveValidationResult {
5113
+ success: boolean;
5114
+ }
5115
+ /**
5116
+ * Emitted for a cross-column drag, after the user releases the item but
5117
+ * **before** any board state is touched — only when
5118
+ * `validateMove` is on. (Off, the default, a cross-column
5119
+ * drop commits immediately, same as a same-column reorder, and this event
5120
+ * never fires at all.) Once opted in, the board waits for `resolve` to be
5121
+ * called before it does anything else. Call it with `{ success: true }` to
5122
+ * let the move proceed (source loses the item, destination gains it,
5123
+ * `columnsChanged` fires with the final state) or `{ success: false }` to
5124
+ * reject it outright (nothing is mutated; since nothing was ever mutated in
5125
+ * the first place, the item is already exactly where it started — see the
5126
+ * component doc for why that's automatic rather than something this event
5127
+ * has to arrange itself). `resolve` can be called synchronously, right
5128
+ * inside your handler, or later — after an `await`, a server round-trip,
5129
+ * whatever the validation needs — the board simply waits either way.
5130
+ *
5131
+ * Only fires for an actual cross-column attempt (`sourceColumn.id !==
5132
+ * destinationColumn.id`); a same-column reorder never touches this event
5133
+ * regardless of `validateMove`. Once that input is on,
5134
+ * binding this event and calling `resolve` becomes mandatory — leave it
5135
+ * unbound and `resolve` is never called, so the move never completes.
5136
+ *
5137
+ * `sourceColumn`/`destinationColumn` are snapshots of each column exactly as
5138
+ * it stood the instant before this fired — still carrying the moved item in
5139
+ * `sourceColumn.items`, not yet in `destinationColumn.items`.
5140
+ */
5141
+ interface BkKanbanItemMoveBetweenColumnsEvent<T extends ColumnItem = ColumnItem> {
5142
+ /** The item being moved, as it stood in its source column pre-move. */
5143
+ item: T;
5144
+ sourceColumn: KanbanColumn<T>;
5145
+ destinationColumn: KanbanColumn<T>;
5146
+ /** Whichever item the moved one would land next to in the destination —
5147
+ * `null` when dropping into an empty column, or at a position with no
5148
+ * neighbor either side (shouldn't happen past an empty column, but kept
5149
+ * nullable rather than asserted). */
5150
+ targetItem: T | null;
5151
+ /** Index within `destinationColumn.items` the item would be inserted at. */
5152
+ targetIndex: number;
5153
+ resolve: (result: BkKanbanItemMoveValidationResult) => void;
5154
+ }
5049
5155
  /**
5050
5156
  * Template context for `columnHeaderActionsTemplate`.
5051
5157
  *
5052
- * `$implicit` and `column` are the same value both are provided so the
5053
- * template can use either `let-column` or the more explicit
5054
- * `let-column="column"`. `trigger()` is there purely for
5055
- * `columnActionTriggered`; most consumers bind their own click handler on
5056
- * their own template instead and never call it.
5158
+ * `$implicit` and `column` are the same value. `addItem` is how a custom
5159
+ * header template wires its own "add" button up to the board's own add
5160
+ * logic (id generation, `sortOrder`/`parentId`, the WIP-limit check, and the
5161
+ * `columnsChanged`/`columnLimitExceeded` emissions) without reimplementing
5162
+ * any of it call it with at least a `label`; an `id` you supply is used
5163
+ * as-is, otherwise one is generated. It's also exactly what the board's own
5164
+ * built-in "+" button (shown whenever `showAddItem` is true, no
5165
+ * `columnHeaderActionsTemplate` content replaces it, and the column's own
5166
+ * `allowAdd` isn't `false`) calls under the hood. `trigger()` is reserved
5167
+ * for `columnActionTriggered`; most consumers bind their own click handler
5168
+ * instead and never call it.
5057
5169
  */
5058
- interface BkKanbanColumnActionsContext {
5059
- $implicit: BkKanbanColumn;
5060
- column: BkKanbanColumn;
5170
+ interface BkKanbanColumnActionsContext<T extends ColumnItem = ColumnItem> {
5171
+ $implicit: KanbanColumn<T>;
5172
+ column: KanbanColumn<T>;
5173
+ addItem: (item: {
5174
+ id?: string;
5175
+ label: string;
5176
+ [key: string]: unknown;
5177
+ }) => void;
5061
5178
  trigger: () => void;
5062
5179
  }
5063
5180
  /** Template context for `emptyStateTemplate`. */
5064
- interface BkKanbanEmptyStateContext {
5065
- $implicit: BkKanbanColumn;
5066
- column: BkKanbanColumn;
5181
+ interface BkKanbanEmptyStateContext<T extends ColumnItem = ColumnItem> {
5182
+ $implicit: KanbanColumn<T>;
5183
+ column: KanbanColumn<T>;
5067
5184
  }
5068
- /** Template context for `cardTemplate`. */
5069
- interface BkKanbanCardContext<T = any> {
5185
+ /**
5186
+ * Template context for `cardTemplate`.
5187
+ *
5188
+ * `removeItem()` is how a fully custom card body wires its own remove
5189
+ * affordance up to the board's own remove logic, the same way
5190
+ * `BkKanbanColumnActionsContext.addItem` does for adding — it's what the
5191
+ * board's own built-in remove button (shown whenever `showRemoveItem` is
5192
+ * true and the column's own `allowRemove` isn't `false`) calls under the
5193
+ * hood. Unlike the built-in button's *visibility*, calling this function is
5194
+ * never gated by `showRemoveItem` — that input only controls whether the
5195
+ * board renders its own button, so a fully custom card body can wire this up
5196
+ * on its own even with the built-in button off (it's still gated by the
5197
+ * column's own `allowRemove`, same as the built-in button, and must handle
5198
+ * its own click/drag isolation when it does — see the component's own
5199
+ * remove button markup in `kanban.html` for the pattern to copy).
5200
+ */
5201
+ interface BkKanbanCardContext<T extends ColumnItem = ColumnItem> {
5070
5202
  $implicit: T;
5071
5203
  card: T;
5072
- column: BkKanbanColumn;
5204
+ column: KanbanColumn<T>;
5073
5205
  index: number;
5206
+ removeItem: () => void;
5074
5207
  }
5075
5208
 
5076
5209
  /**
5077
- * Content-agnostic kanban board: column layout, card positioning and
5078
- * drag-and-drop, and nothing about what a card contains or what a click on
5079
- * one should do.
5210
+ * Kanban board: column layout, item positioning and drag-and-drop, add and
5211
+ * remove, and nothing about what an item's *body* renders as beyond the
5212
+ * fixed `id` / `label` / `sortOrder` / `parentId` every `ColumnItem` carries.
5080
5213
  *
5081
- * The board never renders a card's insides itself `cardTemplate` does that,
5082
- * receiving the card and its column as context:
5214
+ * Columns own their items a single nested tree is the source of truth:
5083
5215
  *
5084
5216
  * <bk-kanban
5085
- * [columns]="columns"
5086
- * [cards]="cards()"
5217
+ * [columns]="columns()"
5087
5218
  * [cardTemplate]="card"
5088
- * (cardMoved)="onCardMoved($event)"
5089
- * (cardClicked)="onCardClicked($event)"
5219
+ * [showAddItem]="true"
5220
+ * [showRemoveItem]="true"
5221
+ * (columnsChanged)="onColumnsChanged($event)"
5222
+ * (itemClicked)="onItemClicked($event)"
5090
5223
  * ></bk-kanban>
5091
5224
  *
5092
5225
  * <ng-template #card let-card let-column="column">
5093
5226
  * <div [bkKanbanClickTarget]="'hours'">{{ card.hours }}h</div>
5094
- * {{ card.title }}
5227
+ * {{ card.label }}
5095
5228
  * </ng-template>
5096
5229
  *
5097
- * That is deliberate, not an oversight — see the package spec's Design
5098
- * Principles. Three consequences fall out of it directly:
5230
+ * Three consequences fall out of that directly:
5099
5231
  *
5100
- * - **Data ownership stays with the caller.** A move is reported through
5101
- * `cardMoved`, not applied to some copy of `cards` this component keeps to
5102
- * itself. Internally it *does* mutate the card objects it was handed
5103
- * (`columnIdField`'s field, and an `order` field used to remember position
5104
- * within a column) so the board redraws in the right place immediately,
5105
- * exactly the way `bk-grid`'s own row-drag mutates `result` in place and
5106
- * renumbers `sortOrder` — but persistence, and reverting the view if a
5107
- * save fails, are the host's job. Reverting is just handing back a
5108
- * `cards` array (or updated objects) that reflect the pre-move state; nothing
5232
+ * - **Data ownership stays with the caller.** Every operation is reported
5233
+ * through `columnsChanged` (only the column(s) actually affected, each
5234
+ * with its complete current `items` column reordering aside, see
5235
+ * `columnReorderPayload`), not applied to some copy of `columns` this
5236
+ * component keeps to itself. Internally it *does* mutate the column/item
5237
+ * objects it was handed (`items`, `sortOrder`, `parentId`) so the board
5238
+ * redraws in the right place immediately — but persistence, and reverting
5239
+ * the view if a save fails, are the host's job. Reverting is just handing
5240
+ * back a `columns` array that reflects the pre-operation state; nothing
5109
5241
  * further to undo on this end.
5110
- * - **Clicks are reported, never acted on.** `cardClicked` says which card and
5111
- * which named region (see `[bkKanbanClickTarget]`); opening a popup or
5242
+ * - **A cross-column move commits immediately by default** same as a
5243
+ * same-column reorder, nothing to bind. Turn on
5244
+ * `validateMove` and that flips: dragging an item onto a
5245
+ * different column then doesn't touch `columns()` at all until the
5246
+ * consumer answers `itemMoveBetweenColumns` — see that event's doc comment
5247
+ * and `onItemDrop` below for the full flow.
5248
+ * - **Clicks are reported, never acted on.** `itemClicked` says which item
5249
+ * and which named region (see `[bkKanbanClickTarget]`); opening a popup or
5112
5250
  * doing nothing at all is entirely up to whoever is listening.
5113
- * - **A card's own shape is never inspected**, beyond the two fields above.
5114
- * Tags, hours, avatars, due dates — the board doesn't know they exist.
5251
+ * - **An item's own shape past the four required fields is never
5252
+ * inspected.** Tags, hours, avatars, due dates — the board doesn't know
5253
+ * they exist. `label` is the one exception: the board reads it for the
5254
+ * built-in add/remove buttons' aria-labels and as the default label an
5255
+ * add gets when no custom `columnHeaderActionsTemplate` supplies one.
5115
5256
  */
5116
- declare class BkKanban<T = any> {
5117
- /** Column/status definitions. Unlimited — the board renders whatever it's given. */
5118
- columns: _angular_core.InputSignal<BkKanbanColumn[]>;
5119
- /** Flat card list. Grouped into columns via `columnIdField`, ordered within
5120
- * a column by an `order` field when the cards carry one, otherwise left in
5121
- * the sequence they appear here. */
5122
- cards: _angular_core.InputSignal<readonly T[]>;
5123
- /** Renders a card's body. Receives `{ $implicit: card, card, column, index }`. */
5257
+ declare class BkKanban<T extends ColumnItem = ColumnItem> {
5258
+ /** Columns, each owning its own `items` — the single source of truth.
5259
+ * Unlimited columns, unlimited items per column; the board renders
5260
+ * whatever it's given. Mutated in place on every drag/add/remove — see
5261
+ * the component doc. */
5262
+ columns: _angular_core.InputSignal<KanbanColumn<T>[]>;
5263
+ /** Renders an item's body. Receives `{ $implicit: card, card, column,
5264
+ * index, removeItem }`. */
5124
5265
  cardTemplate: _angular_core.InputSignal<TemplateRef<BkKanbanCardContext<T>>>;
5125
- /** Field name, or a resolver function, that gives a card's column id.
5126
- *
5127
- * A field name can be both read and written, so a cross-column drop
5128
- * updates it directly. A resolver function is read-only the board still
5129
- * reports the move via `cardMoved`, but leaves updating the source data to
5130
- * the host, since there's nowhere on a function to write the new value. */
5131
- columnIdField: _angular_core.InputSignal<string | ((card: T) => string)>;
5132
- dragEnabled: _angular_core.InputSignal<boolean>;
5133
- dragScope: _angular_core.InputSignal<BkKanbanDragScope>;
5266
+ /** How far (and whether at all) a card can be dragged — see
5267
+ * `BkKanbanMovementRestriction`. One control instead of a separate
5268
+ * on/off switch plus a restriction, so there's nothing to keep in sync
5269
+ * between two inputs: `'none'` is what used to be `dragEnabled: false`. */
5270
+ movementRestriction: _angular_core.InputSignal<BkKanbanMovementRestriction>;
5134
5271
  wipLimitBehavior: _angular_core.InputSignal<BkKanbanWipLimitBehavior>;
5135
5272
  /** Lets columns themselves be dragged into a new order, via their header
5136
- * (`.bk-kanban-column-header` is the drag handle — a card's own drag
5273
+ * (`.bk-kanban-column-header` is the drag handle — an item's own drag
5137
5274
  * keeps working independently, since it's a separate, unconnected drop
5138
5275
  * list one level down). Off by default: reordering the board's own
5139
- * columns is a bigger commitment than reordering cards within it, and not
5276
+ * columns is a bigger commitment than reordering items within it, and not
5140
5277
  * every consumer wants a "To do / In progress / Done" pipeline to be
5141
- * rearrangeable. `columns` is mutated in place the same way `cards` is
5142
- * (see the component doc) — persisting the new order, if it needs to be,
5143
- * is the host's job, done from `columnMoved`. */
5144
- columnDragEnabled: _angular_core.InputSignal<boolean>;
5278
+ * rearrangeable. */
5279
+ dragColumn: _angular_core.InputSignal<boolean>;
5280
+ /** How much a column-reorder `columnsChanged` emission carries — see
5281
+ * `BkKanbanColumnReorderPayload`. Only affects column reordering; every
5282
+ * other operation always emits complete columns regardless. */
5283
+ columnReorderPayload: _angular_core.InputSignal<BkKanbanColumnReorderPayload>;
5284
+ /** Off by default: a cross-column drop commits immediately, exactly like
5285
+ * a same-column reorder — `itemMoveBetweenColumns` never fires, and
5286
+ * there's nothing to bind. Turn this on to gate cross-column moves behind
5287
+ * that event instead — see its own doc comment and `onItemDrop` for the
5288
+ * full validate-before-commit flow this switches on. */
5289
+ validateMove: _angular_core.InputSignal<boolean>;
5145
5290
  /** See `BkKanbanColumnScrollMode`. */
5146
5291
  columnScrollMode: _angular_core.InputSignal<BkKanbanColumnScrollMode>;
5147
- /** Extra class(es) appended to the board's own wrapper — `.bk-kanban-board`
5148
- * in `kanban.html`, the element that directly contains the scrollable
5149
- * region holding every column. Same idea as `columnClass`/`colHeaderClass`
5150
- * one level down: it only adds classes, nothing here replaces bk-kanban's
5151
- * own layout.
5292
+ /** Extra class(es) appended to the board's own wrapper — the element that
5293
+ * directly contains the scrollable region holding every column. Same
5294
+ * idea as `columnClass`/`colHeaderClass` one level down: it only adds
5295
+ * classes, nothing here replaces bk-kanban's own layout.
5152
5296
  *
5153
- * Deliberately kept off the *scrolling* element itself
5154
- * (`.bk-kanban`, one level further in): a `padding` or `border` in here
5155
- * would otherwise become part of what has to be scrolled past, and change
5156
- * where the scrollbar itself ends up sitting relative to the columns —
5157
- * this wrapper exists specifically so `boardClass` can decorate (a
5158
- * background, a border, padding around the whole board) without ever
5159
- * touching the box CDK and `columnScrollMode` are actually scrolling. */
5297
+ * Deliberately kept off the *scrolling* element itself: a `padding` or
5298
+ * `border` in here would otherwise become part of what has to be
5299
+ * scrolled past, and change where the scrollbar itself ends up sitting
5300
+ * relative to the columns this wrapper exists specifically so
5301
+ * `boardClass` can decorate (a background, a border, padding around the
5302
+ * whole board) without ever touching the box CDK and `columnScrollMode`
5303
+ * are actually scrolling. */
5160
5304
  boardClass: _angular_core.InputSignal<string>;
5161
- /** Identity for `@for`'s `track`. Defaults to position in the rendered column. */
5305
+ /** Identity for `@for`'s `track`. Defaults to `card.id` every
5306
+ * `ColumnItem` is guaranteed to have one. */
5162
5307
  trackBy: _angular_core.InputSignal<((card: T) => string | number) | null>;
5163
- /** Rendered in a fixed slot in every column's header e.g. an "add card"
5164
- * button. Receives `BkKanbanColumnActionsContext`. The board owns where it
5165
- * sits; the consumer owns what it does. */
5166
- columnHeaderActionsTemplate: _angular_core.InputSignal<TemplateRef<BkKanbanColumnActionsContext> | null>;
5167
- /** Overrides the built-in "No cards" placeholder for an empty column.
5168
- * Receives `BkKanbanEmptyStateContext`. Takes priority over both
5169
- * `noDataText` and a column's own `noDataText` — see `emptyStateText`. */
5170
- emptyStateTemplate: _angular_core.InputSignal<TemplateRef<BkKanbanEmptyStateContext> | null>;
5171
- /** Board-wide placeholder text for an empty column, used whenever a column
5172
- * doesn't set its own `noDataText`. Only read when `emptyStateTemplate`
5173
- * isn't supplied see `emptyStateText`. */
5174
- noDataText: _angular_core.InputSignal<string>;
5175
- /** Whether a column flags an over-limit drop while still dragging (`'live'`)
5176
- * or only once the drop is attempted (`'on-drop'`). See the doc block on
5177
- * `isLiveOverLimit` for how the two relate to `wipLimitBehavior`. */
5308
+ /** Rendered in a fixed slot in every column's header, alongside the
5309
+ * built-in add button when `showAddItem` is on. Receives
5310
+ * `BkKanbanColumnActionsContext` its `addItem` is the same logic the
5311
+ * built-in button calls, so a fully custom header action can drive it
5312
+ * too instead of reimplementing id generation / limit checks. */
5313
+ columnHeaderActionsTemplate: _angular_core.InputSignal<TemplateRef<BkKanbanColumnActionsContext<T>> | null>;
5314
+ /** Overrides the built-in empty-column placeholder. Receives
5315
+ * `BkKanbanEmptyStateContext`. Takes priority over both `emptyMessage`
5316
+ * and a column's own `emptyMessage`. */
5317
+ emptyStateTemplate: _angular_core.InputSignal<TemplateRef<BkKanbanEmptyStateContext<T>> | null>;
5318
+ /** Board-wide placeholder text for an empty column, used whenever a
5319
+ * column doesn't set its own `emptyMessage`. Only read when
5320
+ * `emptyStateTemplate` isn't supplied. */
5321
+ emptyMessage: _angular_core.InputSignal<string>;
5322
+ /** Whether a column flags an over-limit drop while still dragging
5323
+ * (`'live'`) or only once the drop is attempted (`'on-drop'`). */
5178
5324
  dragPreviewMode: _angular_core.InputSignal<BkKanbanDragPreviewMode>;
5179
- cardMoved: _angular_core.OutputEmitterRef<BkKanbanCardMovedEvent<T>>;
5180
- cardClicked: _angular_core.OutputEmitterRef<BkKanbanCardClickedEvent<T>>;
5325
+ /** Shows a built-in "+" button in every column's header (unless that
5326
+ * column's own `allowAdd` is `false`) when true. It calls the same
5327
+ * `addItem` logic exposed to `columnHeaderActionsTemplate` — with a
5328
+ * default `{ label: 'New item' }` — so out-of-the-box adding works with
5329
+ * zero template configuration. Renders alongside, not instead of,
5330
+ * `columnHeaderActionsTemplate` when both are supplied. */
5331
+ showAddItem: _angular_core.InputSignal<boolean>;
5332
+ /** Shows a built-in remove button on every card (unless that card's
5333
+ * column has `allowRemove: false`) when true — board-owned chrome, not
5334
+ * part of `cardTemplate`, so click/drag isolation is guaranteed by the
5335
+ * board itself rather than left to every consumer's own template to get
5336
+ * right. */
5337
+ showRemoveItem: _angular_core.InputSignal<boolean>;
5338
+ /** Fires after any operation that changes the board. See
5339
+ * `BkKanbanColumnsChangedEvent`'s doc comment for how to tell which
5340
+ * operation happened from the metadata field a returned column carries. */
5341
+ columnsChanged: _angular_core.OutputEmitterRef<BkKanbanColumnsChangedEvent<T>>;
5181
5342
  columnLimitExceeded: _angular_core.OutputEmitterRef<BkKanbanColumnLimitExceededEvent>;
5182
- /** A column reorder completed — see `columnDragEnabled`. */
5183
- columnMoved: _angular_core.OutputEmitterRef<BkKanbanColumnMovedEvent>;
5343
+ itemClicked: _angular_core.OutputEmitterRef<BkKanbanItemClickedEvent<T>>;
5344
+ /** Fires for a cross-column drag before any state changes — only when
5345
+ * `validateMove` is on; see the event's own doc comment
5346
+ * and `onItemDrop`. Must be bound (and resolved) once that's on, or
5347
+ * cross-column drag-and-drop stops having any effect. */
5348
+ itemMoveBetweenColumns: _angular_core.OutputEmitterRef<BkKanbanItemMoveBetweenColumnsEvent<T>>;
5184
5349
  /** Reserved — see `BkKanbanColumnActionsContext.trigger`. Most consumers
5185
5350
  * bind a click handler directly on their own header-actions template and
5186
5351
  * never reach for this. */
5187
5352
  columnActionTriggered: _angular_core.OutputEmitterRef<BkKanbanColumnActionTriggeredEvent>;
5188
5353
  /**
5189
- * Bumped after every in-place mutation (`setColumnId`, `renumber`) so
5190
- * `grouped` re-runs even though neither `cards()`'s array identity nor any
5191
- * card's object identity changed — the same trick as the table demo's
5192
- * `pgSelectionTick`, needed for exactly the same reason: signals only
5193
- * notice a new reference, and mutating fields in place is what lets a drop
5194
- * redraw in the right place without waiting on the host to hand back a
5195
- * whole new array.
5354
+ * Bumped after every in-place mutation so the template re-reads
5355
+ * `boardColumns()` even though neither `columns()`'s own array identity
5356
+ * nor any column/item's object identity necessarily changed — mutating
5357
+ * fields in place is what lets a drop/add/remove redraw in the right
5358
+ * place without waiting on the host to hand back a whole new array.
5196
5359
  */
5197
5360
  private readonly structureTick;
5198
- /** Cards grouped by column id, each group in `order` order when present
5199
- * (falling back to their sequence in `cards()` otherwise). Cards whose
5200
- * resolved column id matches no known column are dropped silently rather
5201
- * than rendered nowhere. */
5202
- private readonly grouped;
5203
- /** The cards to render for one column, in display order. */
5204
- cardsFor(columnId: string): T[];
5205
- private resolveColumnId;
5206
- private cardOrder;
5361
+ /** What the template actually iterates a signal that re-evaluates on
5362
+ * every `structureTick` bump, so in-place mutations to `columns()`'s own
5363
+ * items/sortOrder are always picked up. */
5364
+ readonly boardColumns: _angular_core.Signal<KanbanColumn<T>[]>;
5207
5365
  resolveTrackBy(card: T, index: number): string | number;
5366
+ private columnById;
5208
5367
  /** Column-header count badge, flagged once the column is over its limit —
5209
5368
  * only while `wipLimitBehavior` is `'warn'`; `'block'` should never let a
5210
5369
  * column get there and `'none'` treats the number as pure information. */
5211
- isColumnOverLimit(column: BkKanbanColumn): boolean;
5370
+ isColumnOverLimit(column: KanbanColumn<T>): boolean;
5212
5371
  /** "3" with no limit, "3 / 5" with one — the header count badge's label. */
5213
- columnCountLabel(column: BkKanbanColumn): string;
5214
- columnActionsContext(column: BkKanbanColumn): BkKanbanColumnActionsContext;
5215
- emptyStateContext(column: BkKanbanColumn): BkKanbanEmptyStateContext;
5372
+ columnCountLabel(column: KanbanColumn<T>): string;
5373
+ /** Whether the built-in "+" button should render for this column
5374
+ * `showAddItem` board-wide, unless this column opted itself out. */
5375
+ showAddButton(column: KanbanColumn<T>): boolean;
5376
+ /** Whether the built-in remove button should render for this card's
5377
+ * column — `showRemoveItem` board-wide, unless this column opted
5378
+ * itself out. */
5379
+ showRemoveButton(column: KanbanColumn<T>): boolean;
5380
+ columnActionsContext(column: KanbanColumn<T>): BkKanbanColumnActionsContext<T>;
5381
+ emptyStateContext(column: KanbanColumn<T>): BkKanbanEmptyStateContext<T>;
5216
5382
  /** Text for the built-in empty-column placeholder: a column's own
5217
- * `noDataText` when it sets one, else the board-wide `noDataText` input.
5218
- * Only consulted when `emptyStateTemplate` isn't supplied — the template
5219
- * wins outright once given, exactly like `emptyStateContext` itself. */
5220
- emptyStateText(column: BkKanbanColumn): string;
5221
- cardContext(card: T, column: BkKanbanColumn, index: number): BkKanbanCardContext<T>;
5222
- /** Which columns a column's drop list connects to, per `dragScope`. */
5383
+ * `emptyMessage` when it sets one, else the board-wide `emptyMessage`
5384
+ * input. Only consulted when `emptyStateTemplate` isn't supplied. */
5385
+ emptyMessageFor(column: KanbanColumn<T>): string;
5386
+ cardContext(card: T, column: KanbanColumn<T>, index: number): BkKanbanCardContext<T>;
5387
+ /** Whether any card dragging is active at all — false only for
5388
+ * `movementRestriction: 'none'`. Drives both the column body's own
5389
+ * `cdkDropListDisabled` and each card's own `cdkDragDisabled`. */
5390
+ readonly dragActive: _angular_core.Signal<boolean>;
5391
+ /** Which columns a column's drop list connects to, per `movementRestriction`. */
5223
5392
  connectedListIds(columnId: string): string[];
5224
- /** `'cross'` means *only* moves between columns — reordering within one has
5225
- * to be switched off at the list level, since a scoped-out `connectedTo`
5226
- * alone still leaves the list free to sort itself. */
5393
+ /** `'between'` means *only* moves between columns — reordering
5394
+ * within one has to be switched off at the list level, since a
5395
+ * scoped-out `connectedTo` alone still leaves the list free to sort
5396
+ * itself. `'none'` doesn't need to appear here too: `cdkDropListDisabled`
5397
+ * (bound to `dragActive`) already switches the whole list off, so whether
5398
+ * sorting specifically is "disabled" on top of that is moot. */
5227
5399
  readonly sortingDisabled: _angular_core.Signal<boolean>;
5228
5400
  /**
5229
- * `columns` is reordered in place — same trick as `cards` (see the
5401
+ * `columns` is reordered in place — same trick as items (see the
5230
5402
  * component doc): the array reference the host handed in is mutated
5231
- * directly (`moveItemInArray`) rather than swapped for a new one, so the
5232
- * next render just reflects the new order. There's no `structureTick`-style
5233
- * signal to bump here the way card moves need one nothing derives
5234
- * columns through a `computed()` the way `cardsFor` derives through
5235
- * `grouped`; the template reads `columns()` directly in its `@for`, so the
5236
- * drop event's own change detection pass is enough.
5237
- */
5238
- onColumnDrop(event: CdkDragDrop<BkKanbanColumn[]>): void;
5403
+ * directly (`moveItemInArray`) rather than swapped for a new one.
5404
+ * `sortOrder` is then renumbered for the whole array, and unlike every
5405
+ * other operation, which reports only the column(s) actually affected
5406
+ * *every* column in the board is reported here, in its resulting order,
5407
+ * shaped per `columnReorderPayload`: consumers persisting a full column
5408
+ * order need to see the whole list, not just the entries that happened to
5409
+ * shift.
5410
+ */
5411
+ onColumnDrop(event: CdkDragDrop<KanbanColumn<T>[]>): void;
5239
5412
  private readonly dragSourceColumnId;
5240
5413
  private readonly hoveredOverLimitColumnId;
5241
- onDragStarted(column: BkKanbanColumn): void;
5414
+ onDragStarted(column: KanbanColumn<T>): void;
5242
5415
  onDragEnded(): void;
5243
5416
  /**
5244
- * Live WIP-limit preview (§12 of the spec): as the dragged card enters a
5245
- * column, work out whether *landing* here would exceed its `wipLimit` and
5246
- * flag it immediately — before release, and independent of
5247
- * `wipLimitBehavior`, which only governs what happens once the card is
5248
- * actually dropped. A card re-entering its own source column doesn't grow
5249
- * that column's count, so only a genuine cross-column entry counts as +1.
5250
- */
5251
- onListEntered(event: CdkDragEnter<T[]>, column: BkKanbanColumn): void;
5252
- onListExited(column: BkKanbanColumn): void;
5253
- isLiveOverLimit(column: BkKanbanColumn): boolean;
5254
- onDrop(event: CdkDragDrop<T[]>, column: BkKanbanColumn): void;
5255
- private setColumnId;
5417
+ * Live WIP-limit preview: as the dragged item enters a column, work out
5418
+ * whether *landing* here would exceed its `limit` and flag it immediately
5419
+ * — before release, and independent of `wipLimitBehavior`, which only
5420
+ * governs what happens once the item is actually dropped. An item
5421
+ * re-entering its own source column doesn't grow that column's count, so
5422
+ * only a genuine cross-column entry counts as +1.
5423
+ */
5424
+ onListEntered(event: CdkDragEnter<T[]>, column: KanbanColumn<T>): void;
5425
+ onListExited(column: KanbanColumn<T>): void;
5426
+ isLiveOverLimit(column: KanbanColumn<T>): boolean;
5256
5427
  private renumber;
5428
+ /**
5429
+ * Same-column reorder commits immediately (nothing to validate — the
5430
+ * column's own membership never changes) and reports the one affected
5431
+ * column with its complete, re-ordered `items`. A genuine cross-column
5432
+ * drop is a different story: see the second half of this method and
5433
+ * `itemMoveBetweenColumns`'s own doc comment for why it does *not*
5434
+ * mutate anything here.
5435
+ */
5436
+ onItemDrop(event: CdkDragDrop<T[]>, column: KanbanColumn<T>): void;
5437
+ /** The actual cross-column mutation, run only once `resolve({ success:
5438
+ * true })` comes back from `itemMoveBetweenColumns` — see `onItemDrop`. */
5439
+ private commitCrossColumnMove;
5440
+ private generateId;
5441
+ /** Adds a new item to a column — the logic backing both the built-in "+"
5442
+ * button (`showAddItem`) and `BkKanbanColumnActionsContext.addItem`
5443
+ * for a fully custom header template. Supply at least `label`; an `id`
5444
+ * given is used as-is, otherwise one is generated. Respects
5445
+ * `wipLimitBehavior` exactly like a cross-column drop does, and does
5446
+ * nothing at all for a column with `allowAdd: false` or an empty/missing
5447
+ * `label` — see §11. */
5448
+ addItem(columnId: string, data: {
5449
+ id?: string;
5450
+ label: string;
5451
+ [key: string]: unknown;
5452
+ }): void;
5453
+ /** Removes an item from a column — the logic backing both the built-in
5454
+ * remove button (`showRemoveItem`) and `BkKanbanCardContext.removeItem`
5455
+ * for a fully custom card body. Does nothing for a column with
5456
+ * `allowRemove: false`, or if the item isn't actually there. */
5457
+ removeItem(columnId: string, itemId: string): void;
5458
+ onAddButtonClick(column: KanbanColumn<T>): void;
5459
+ /** The remove button's own click handler. It's a `bk-icon-button`, so its
5460
+ * own `(clicked)` output only ever carries a boolean, not the originating
5461
+ * event — isolation happens entirely in the template instead, via native
5462
+ * `mousedown`/`pointerdown`/`click` listeners bound directly on the
5463
+ * `<bk-icon-button>` host: `mousedown`/`pointerdown` are stopped before
5464
+ * CDK's drag-start detection (which listens on the card itself, an
5465
+ * ancestor of this button) ever sees the press, and stopping `click` too
5466
+ * keeps it from also reaching `onItemClick` on the card underneath. See
5467
+ * §18/§12 of the spec this shipped against. */
5468
+ onRemoveButtonClick(column: KanbanColumn<T>, card: T): void;
5469
+ private readonly hoveredRemoveItemId;
5470
+ removeButtonVariant(card: T): IconButtonVariant;
5471
+ removeButtonIcon(card: T): string;
5472
+ onRemoveButtonHover(card: T, hovered: boolean): void;
5257
5473
  /**
5258
5474
  * One listener per card resolves both the whole-card click and every
5259
5475
  * `[bkKanbanClickTarget]` region: `closest()` from the actual event target
5260
5476
  * finds the nearest marked ancestor within this card, or there isn't one
5261
5477
  * and it's a plain card click. Either way this fires exactly once — never
5262
- * once for the region and again for the card underneath it.
5478
+ * once for the region and again for the card underneath it. The remove
5479
+ * button (when shown) stops its own click from ever reaching here — see
5480
+ * `onRemoveButtonClick`.
5263
5481
  */
5264
- onCardClick(event: MouseEvent, card: T): void;
5482
+ onItemClick(event: MouseEvent, card: T): void;
5265
5483
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<BkKanban<any>, never>;
5266
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkKanban<any>, "bk-kanban", ["bkKanban"], { "columns": { "alias": "columns"; "required": true; "isSignal": true; }; "cards": { "alias": "cards"; "required": true; "isSignal": true; }; "cardTemplate": { "alias": "cardTemplate"; "required": true; "isSignal": true; }; "columnIdField": { "alias": "columnIdField"; "required": false; "isSignal": true; }; "dragEnabled": { "alias": "dragEnabled"; "required": false; "isSignal": true; }; "dragScope": { "alias": "dragScope"; "required": false; "isSignal": true; }; "wipLimitBehavior": { "alias": "wipLimitBehavior"; "required": false; "isSignal": true; }; "columnDragEnabled": { "alias": "columnDragEnabled"; "required": false; "isSignal": true; }; "columnScrollMode": { "alias": "columnScrollMode"; "required": false; "isSignal": true; }; "boardClass": { "alias": "boardClass"; "required": false; "isSignal": true; }; "trackBy": { "alias": "trackBy"; "required": false; "isSignal": true; }; "columnHeaderActionsTemplate": { "alias": "columnHeaderActionsTemplate"; "required": false; "isSignal": true; }; "emptyStateTemplate": { "alias": "emptyStateTemplate"; "required": false; "isSignal": true; }; "noDataText": { "alias": "noDataText"; "required": false; "isSignal": true; }; "dragPreviewMode": { "alias": "dragPreviewMode"; "required": false; "isSignal": true; }; }, { "cardMoved": "cardMoved"; "cardClicked": "cardClicked"; "columnLimitExceeded": "columnLimitExceeded"; "columnMoved": "columnMoved"; "columnActionTriggered": "columnActionTriggered"; }, never, never, true, never>;
5484
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<BkKanban<any>, "bk-kanban", ["bkKanban"], { "columns": { "alias": "columns"; "required": true; "isSignal": true; }; "cardTemplate": { "alias": "cardTemplate"; "required": true; "isSignal": true; }; "movementRestriction": { "alias": "movementRestriction"; "required": false; "isSignal": true; }; "wipLimitBehavior": { "alias": "wipLimitBehavior"; "required": false; "isSignal": true; }; "dragColumn": { "alias": "dragColumn"; "required": false; "isSignal": true; }; "columnReorderPayload": { "alias": "columnReorderPayload"; "required": false; "isSignal": true; }; "validateMove": { "alias": "validateMove"; "required": false; "isSignal": true; }; "columnScrollMode": { "alias": "columnScrollMode"; "required": false; "isSignal": true; }; "boardClass": { "alias": "boardClass"; "required": false; "isSignal": true; }; "trackBy": { "alias": "trackBy"; "required": false; "isSignal": true; }; "columnHeaderActionsTemplate": { "alias": "columnHeaderActionsTemplate"; "required": false; "isSignal": true; }; "emptyStateTemplate": { "alias": "emptyStateTemplate"; "required": false; "isSignal": true; }; "emptyMessage": { "alias": "emptyMessage"; "required": false; "isSignal": true; }; "dragPreviewMode": { "alias": "dragPreviewMode"; "required": false; "isSignal": true; }; "showAddItem": { "alias": "showAddItem"; "required": false; "isSignal": true; }; "showRemoveItem": { "alias": "showRemoveItem"; "required": false; "isSignal": true; }; }, { "columnsChanged": "columnsChanged"; "columnLimitExceeded": "columnLimitExceeded"; "itemClicked": "itemClicked"; "itemMoveBetweenColumns": "itemMoveBetweenColumns"; "columnActionTriggered": "columnActionTriggered"; }, never, never, true, never>;
5267
5485
  }
5268
5486
 
5269
5487
  /**
@@ -5294,4 +5512,4 @@ declare class BkKanbanClickTarget {
5294
5512
  }
5295
5513
 
5296
5514
  export { ARROW_CORNER_GAP, BKTooltipDirective, BK_DEFAULT_DIALOG_CONFIG, BK_DIALOG_DATA, BK_DIALOG_GLOBAL_CONFIG, BK_TABLE, BkAvatar, BkAvatarGroup, BkAvatarUploader, BkBadge, BkBreadcrumb, BkButton, BkButtonGroup, BkCalendarManagerService, BkCheckbox, BkColumnFilterService, BkColumnSelect, BkCustomCalendar, BkDialogActions, BkDialogClose, BkDialogContent, BkDialogModule, BkDialogRef, BkDialogService, BkDialogTitle, BkDragHandle, BkDropdown, BkFileCard, BkFilePicker, BkGrid, BkHierarchicalSelect, BkIconButton, BkInput, BkInputChips, BkKanban, BkKanbanClickTarget, BkLoader, BkMenu, BkPagination, BkPill, BkPopover, BkRadioButton, BkScheduledDatePicker, BkSelect, BkSpinner, BkTable, BkTableDrag, BkTableFooter, BkTableSummary, BkTableTitle, BkTabs, BkTd, BkTextarea, BkTh, BkTimePicker, BkToastr, BkToastrService, BkToggle, BkTooltipInteractionService, BkTrExpand, BkTreeDrag, BkValidator, BkVirtualScroll, BrickclayIcons, BrickclayLib, CalendarModule, CalendarMonth, CalendarSelection, CalendarWeekday, ColumnFilterOption, DEFAULT_COUNTRY_OPTIONS, NEUTRAL_APPEARANCE, OPPOSITE_SIDE, POPOVER_PLACEMENTS, clamp, containsBkTreeNode, deriveAppearance, flattenBkTreeData, getDialogBackdropAnimation, getDialogPanelAnimation, joinPlacement, moveBkTreeNode, normalizeBkTableSize, parseColor, splitPlacement };
5297
- export type { AvatarFallback, AvatarGroupItem, AvatarSize, AvatarVariant, BadgeColor, BadgeSize, BadgeVariant, BkAnimationKeyframes, BkAvatarFallback, BkAvatarSize, BkCellAlign, BkColumnRef, BkColumnSelectPosition, BkDialogAnimation, BkDialogConfig, BkDialogPosition, BkFilterFn, BkFilterOption, BkInputAutoCapitalize, BkInputAutoComplete, BkInputMode, BkInputSize, BkInputType, BkKanbanCardClickedEvent, BkKanbanCardContext, BkKanbanCardMovedEvent, BkKanbanColumn, BkKanbanColumnActionTriggeredEvent, BkKanbanColumnActionsContext, BkKanbanColumnLimitExceededEvent, BkKanbanColumnMovedEvent, BkKanbanColumnScrollMode, BkKanbanDragPreviewMode, BkKanbanDragScope, BkKanbanEmptyStateContext, BkKanbanWipLimitBehavior, BkLoaderVariant, BkPageSize, BkPageSizeVisibility, BkSelectGridColumn, BkSortDirection, BkSortFn, BkSortOrder, BkTableNoResult, BkTableQueryParams, BkTableScroll, BkTableSelection, BkTableSize, BkTableSizeToken, BkTextAreaAutoCapitalize, BkTextAreaAutoComplete, BkTextAreaInputMode, BkTooltipDismissible, BkTreeDragScope, BkTreeDropEvent, BkTreeDropPosition, BkTreeRow, BreadcrumbItem, ButtonSize, ButtonVariant, CalendarRange, ColorAppearance, ColorFill, CountryOption, CustomRangesConfig, DotPosition, DotStatus, DropdownItem, DropdownPlacement, DropdownSize, DropdownTrigger, DropdownVariant, FileState, GroupItem, GroupMode, HierarchicalNode, IconButtonSize, IconButtonVariant, IconOrientation, MenuItem, MenuOrientation, MenuPopupSide, MenuSize, MenuSubmenuMode, MenuTrigger, PillColor, PillSize, PillVariant, PopoverAlign, PopoverPlacement, PopoverSide, ScheduledDateSelection, SortDirection, SpinnerSize, TabIconDirection, TabItem, TableAction, TableBadge, TableColumn, TableIcon, TableRows, TabsColors, TabsOrientation, TabsVariant, TimeConfiguration, ToastConfig, ToastMessage, ToastMethodOptions, ToastPosition, ToastSeverity };
5515
+ export type { AvatarFallback, AvatarGroupItem, AvatarSize, AvatarVariant, BadgeColor, BadgeSize, BadgeVariant, BkAnimationKeyframes, BkAvatarFallback, BkAvatarSize, BkCellAlign, BkColumnRef, BkColumnSelectPosition, BkDialogAnimation, BkDialogConfig, BkDialogPosition, BkFilterFn, BkFilterOption, BkInputAutoCapitalize, BkInputAutoComplete, BkInputMode, BkInputSize, BkInputType, BkKanbanCardContext, BkKanbanColumnActionTriggeredEvent, BkKanbanColumnActionsContext, BkKanbanColumnLimitExceededEvent, BkKanbanColumnReorderPayload, BkKanbanColumnScrollMode, BkKanbanColumnSummary, BkKanbanColumnsChangedEvent, BkKanbanDragPreviewMode, BkKanbanEmptyStateContext, BkKanbanItemClickedEvent, BkKanbanItemMoveBetweenColumnsEvent, BkKanbanItemMoveValidationResult, BkKanbanMovementRestriction, BkKanbanWipLimitBehavior, BkLoaderVariant, BkPageSize, BkPageSizeVisibility, BkSelectGridColumn, BkSortDirection, BkSortFn, BkSortOrder, BkTableNoResult, BkTableQueryParams, BkTableScroll, BkTableSelection, BkTableSize, BkTableSizeToken, BkTextAreaAutoCapitalize, BkTextAreaAutoComplete, BkTextAreaInputMode, BkTooltipDismissible, BkTreeDragScope, BkTreeDropEvent, BkTreeDropPosition, BkTreeRow, BreadcrumbItem, ButtonSize, ButtonVariant, CalendarRange, ColorAppearance, ColorFill, ColumnItem, CountryOption, CustomRangesConfig, DotPosition, DotStatus, DropdownItem, DropdownPlacement, DropdownSize, DropdownTrigger, DropdownVariant, FileState, GroupItem, GroupMode, HierarchicalNode, IconButtonSize, IconButtonVariant, IconOrientation, KanbanColumn, MenuItem, MenuOrientation, MenuPopupSide, MenuSize, MenuSubmenuMode, MenuTrigger, PillColor, PillSize, PillVariant, PopoverAlign, PopoverPlacement, PopoverSide, ScheduledDateSelection, SortDirection, SpinnerSize, TabIconDirection, TabItem, TableAction, TableBadge, TableColumn, TableIcon, TableRows, TabsColors, TabsOrientation, TabsVariant, TimeConfiguration, ToastConfig, ToastMessage, ToastMethodOptions, ToastPosition, ToastSeverity };