@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/fesm2022/brickclay-org-ui.mjs +396 -205
- package/fesm2022/brickclay-org-ui.mjs.map +1 -1
- package/index.d.ts +474 -256
- package/package.json +1 -1
- package/src/assets/images/icons/global/popup-cross-white.svg +11 -0
- package/src/assets/images/icons/global/popup-cross.svg +4 -0
- package/src/lib/kanban/component/kanban/kanban.css +72 -18
|
@@ -14078,165 +14078,173 @@ const BK_TABLE = [
|
|
|
14078
14078
|
];
|
|
14079
14079
|
|
|
14080
14080
|
/**
|
|
14081
|
-
*
|
|
14082
|
-
*
|
|
14083
|
-
*
|
|
14081
|
+
* Kanban board: column layout, item positioning and drag-and-drop, add and
|
|
14082
|
+
* remove, and nothing about what an item's *body* renders as beyond the
|
|
14083
|
+
* fixed `id` / `label` / `sortOrder` / `parentId` every `ColumnItem` carries.
|
|
14084
14084
|
*
|
|
14085
|
-
*
|
|
14086
|
-
* receiving the card and its column as context:
|
|
14085
|
+
* Columns own their items — a single nested tree is the source of truth:
|
|
14087
14086
|
*
|
|
14088
14087
|
* <bk-kanban
|
|
14089
|
-
* [columns]="columns"
|
|
14090
|
-
* [cards]="cards()"
|
|
14088
|
+
* [columns]="columns()"
|
|
14091
14089
|
* [cardTemplate]="card"
|
|
14092
|
-
*
|
|
14093
|
-
*
|
|
14090
|
+
* [showAddItem]="true"
|
|
14091
|
+
* [showRemoveItem]="true"
|
|
14092
|
+
* (columnsChanged)="onColumnsChanged($event)"
|
|
14093
|
+
* (itemClicked)="onItemClicked($event)"
|
|
14094
14094
|
* ></bk-kanban>
|
|
14095
14095
|
*
|
|
14096
14096
|
* <ng-template #card let-card let-column="column">
|
|
14097
14097
|
* <div [bkKanbanClickTarget]="'hours'">{{ card.hours }}h</div>
|
|
14098
|
-
* {{ card.
|
|
14098
|
+
* {{ card.label }}
|
|
14099
14099
|
* </ng-template>
|
|
14100
14100
|
*
|
|
14101
|
-
*
|
|
14102
|
-
* Principles. Three consequences fall out of it directly:
|
|
14101
|
+
* Three consequences fall out of that directly:
|
|
14103
14102
|
*
|
|
14104
|
-
* - **Data ownership stays with the caller.**
|
|
14105
|
-
* `
|
|
14106
|
-
*
|
|
14107
|
-
*
|
|
14108
|
-
*
|
|
14109
|
-
*
|
|
14110
|
-
*
|
|
14111
|
-
* save fails, are the host's job. Reverting is just handing
|
|
14112
|
-
* `
|
|
14103
|
+
* - **Data ownership stays with the caller.** Every operation is reported
|
|
14104
|
+
* through `columnsChanged` (only the column(s) actually affected, each
|
|
14105
|
+
* with its complete current `items` — column reordering aside, see
|
|
14106
|
+
* `columnReorderPayload`), not applied to some copy of `columns` this
|
|
14107
|
+
* component keeps to itself. Internally it *does* mutate the column/item
|
|
14108
|
+
* objects it was handed (`items`, `sortOrder`, `parentId`) so the board
|
|
14109
|
+
* redraws in the right place immediately — but persistence, and reverting
|
|
14110
|
+
* the view if a save fails, are the host's job. Reverting is just handing
|
|
14111
|
+
* back a `columns` array that reflects the pre-operation state; nothing
|
|
14113
14112
|
* further to undo on this end.
|
|
14114
|
-
* - **
|
|
14115
|
-
*
|
|
14113
|
+
* - **A cross-column move commits immediately by default** — same as a
|
|
14114
|
+
* same-column reorder, nothing to bind. Turn on
|
|
14115
|
+
* `validateMove` and that flips: dragging an item onto a
|
|
14116
|
+
* different column then doesn't touch `columns()` at all until the
|
|
14117
|
+
* consumer answers `itemMoveBetweenColumns` — see that event's doc comment
|
|
14118
|
+
* and `onItemDrop` below for the full flow.
|
|
14119
|
+
* - **Clicks are reported, never acted on.** `itemClicked` says which item
|
|
14120
|
+
* and which named region (see `[bkKanbanClickTarget]`); opening a popup or
|
|
14116
14121
|
* doing nothing at all is entirely up to whoever is listening.
|
|
14117
|
-
* - **
|
|
14118
|
-
* Tags, hours, avatars, due dates — the board doesn't know
|
|
14122
|
+
* - **An item's own shape past the four required fields is never
|
|
14123
|
+
* inspected.** Tags, hours, avatars, due dates — the board doesn't know
|
|
14124
|
+
* they exist. `label` is the one exception: the board reads it for the
|
|
14125
|
+
* built-in add/remove buttons' aria-labels and as the default label an
|
|
14126
|
+
* add gets when no custom `columnHeaderActionsTemplate` supplies one.
|
|
14119
14127
|
*/
|
|
14120
14128
|
class BkKanban {
|
|
14121
14129
|
/* ================= Inputs ================= */
|
|
14122
|
-
/**
|
|
14130
|
+
/** Columns, each owning its own `items` — the single source of truth.
|
|
14131
|
+
* Unlimited columns, unlimited items per column; the board renders
|
|
14132
|
+
* whatever it's given. Mutated in place on every drag/add/remove — see
|
|
14133
|
+
* the component doc. */
|
|
14123
14134
|
columns = input.required(...(ngDevMode ? [{ debugName: "columns" }] : []));
|
|
14124
|
-
/**
|
|
14125
|
-
*
|
|
14126
|
-
* the sequence they appear here. */
|
|
14127
|
-
cards = input.required(...(ngDevMode ? [{ debugName: "cards" }] : []));
|
|
14128
|
-
/** Renders a card's body. Receives `{ $implicit: card, card, column, index }`. */
|
|
14135
|
+
/** Renders an item's body. Receives `{ $implicit: card, card, column,
|
|
14136
|
+
* index, removeItem }`. */
|
|
14129
14137
|
cardTemplate = input.required(...(ngDevMode ? [{ debugName: "cardTemplate" }] : []));
|
|
14130
|
-
/**
|
|
14131
|
-
*
|
|
14132
|
-
*
|
|
14133
|
-
*
|
|
14134
|
-
|
|
14135
|
-
* the host, since there's nowhere on a function to write the new value. */
|
|
14136
|
-
columnIdField = input('columnId', ...(ngDevMode ? [{ debugName: "columnIdField" }] : []));
|
|
14137
|
-
dragEnabled = input(true, ...(ngDevMode ? [{ debugName: "dragEnabled" }] : []));
|
|
14138
|
-
dragScope = input('both', ...(ngDevMode ? [{ debugName: "dragScope" }] : []));
|
|
14138
|
+
/** How far (and whether at all) a card can be dragged — see
|
|
14139
|
+
* `BkKanbanMovementRestriction`. One control instead of a separate
|
|
14140
|
+
* on/off switch plus a restriction, so there's nothing to keep in sync
|
|
14141
|
+
* between two inputs: `'none'` is what used to be `dragEnabled: false`. */
|
|
14142
|
+
movementRestriction = input('both', ...(ngDevMode ? [{ debugName: "movementRestriction" }] : []));
|
|
14139
14143
|
wipLimitBehavior = input('warn', ...(ngDevMode ? [{ debugName: "wipLimitBehavior" }] : []));
|
|
14140
14144
|
/** Lets columns themselves be dragged into a new order, via their header
|
|
14141
|
-
* (`.bk-kanban-column-header` is the drag handle —
|
|
14145
|
+
* (`.bk-kanban-column-header` is the drag handle — an item's own drag
|
|
14142
14146
|
* keeps working independently, since it's a separate, unconnected drop
|
|
14143
14147
|
* list one level down). Off by default: reordering the board's own
|
|
14144
|
-
* columns is a bigger commitment than reordering
|
|
14148
|
+
* columns is a bigger commitment than reordering items within it, and not
|
|
14145
14149
|
* every consumer wants a "To do / In progress / Done" pipeline to be
|
|
14146
|
-
* rearrangeable.
|
|
14147
|
-
|
|
14148
|
-
|
|
14149
|
-
|
|
14150
|
+
* rearrangeable. */
|
|
14151
|
+
dragColumn = input(false, ...(ngDevMode ? [{ debugName: "dragColumn" }] : []));
|
|
14152
|
+
/** How much a column-reorder `columnsChanged` emission carries — see
|
|
14153
|
+
* `BkKanbanColumnReorderPayload`. Only affects column reordering; every
|
|
14154
|
+
* other operation always emits complete columns regardless. */
|
|
14155
|
+
columnReorderPayload = input('complete', ...(ngDevMode ? [{ debugName: "columnReorderPayload" }] : []));
|
|
14156
|
+
/** Off by default: a cross-column drop commits immediately, exactly like
|
|
14157
|
+
* a same-column reorder — `itemMoveBetweenColumns` never fires, and
|
|
14158
|
+
* there's nothing to bind. Turn this on to gate cross-column moves behind
|
|
14159
|
+
* that event instead — see its own doc comment and `onItemDrop` for the
|
|
14160
|
+
* full validate-before-commit flow this switches on. */
|
|
14161
|
+
validateMove = input(false, ...(ngDevMode ? [{ debugName: "validateMove" }] : []));
|
|
14150
14162
|
/** See `BkKanbanColumnScrollMode`. */
|
|
14151
|
-
columnScrollMode = input('
|
|
14152
|
-
/** Extra class(es) appended to the board's own wrapper —
|
|
14153
|
-
*
|
|
14154
|
-
*
|
|
14155
|
-
*
|
|
14156
|
-
* own layout.
|
|
14163
|
+
columnScrollMode = input('column', ...(ngDevMode ? [{ debugName: "columnScrollMode" }] : []));
|
|
14164
|
+
/** Extra class(es) appended to the board's own wrapper — the element that
|
|
14165
|
+
* directly contains the scrollable region holding every column. Same
|
|
14166
|
+
* idea as `columnClass`/`colHeaderClass` one level down: it only adds
|
|
14167
|
+
* classes, nothing here replaces bk-kanban's own layout.
|
|
14157
14168
|
*
|
|
14158
|
-
* Deliberately kept off the *scrolling* element itself
|
|
14159
|
-
*
|
|
14160
|
-
*
|
|
14161
|
-
*
|
|
14162
|
-
*
|
|
14163
|
-
*
|
|
14164
|
-
*
|
|
14169
|
+
* Deliberately kept off the *scrolling* element itself: a `padding` or
|
|
14170
|
+
* `border` in here would otherwise become part of what has to be
|
|
14171
|
+
* scrolled past, and change where the scrollbar itself ends up sitting
|
|
14172
|
+
* relative to the columns — this wrapper exists specifically so
|
|
14173
|
+
* `boardClass` can decorate (a background, a border, padding around the
|
|
14174
|
+
* whole board) without ever touching the box CDK and `columnScrollMode`
|
|
14175
|
+
* are actually scrolling. */
|
|
14165
14176
|
boardClass = input('', ...(ngDevMode ? [{ debugName: "boardClass" }] : []));
|
|
14166
|
-
/** Identity for `@for`'s `track`. Defaults to
|
|
14177
|
+
/** Identity for `@for`'s `track`. Defaults to `card.id` — every
|
|
14178
|
+
* `ColumnItem` is guaranteed to have one. */
|
|
14167
14179
|
trackBy = input(null, ...(ngDevMode ? [{ debugName: "trackBy" }] : []));
|
|
14168
|
-
/** Rendered in a fixed slot in every column's header
|
|
14169
|
-
* button
|
|
14170
|
-
*
|
|
14180
|
+
/** Rendered in a fixed slot in every column's header, alongside the
|
|
14181
|
+
* built-in add button when `showAddItem` is on. Receives
|
|
14182
|
+
* `BkKanbanColumnActionsContext` — its `addItem` is the same logic the
|
|
14183
|
+
* built-in button calls, so a fully custom header action can drive it
|
|
14184
|
+
* too instead of reimplementing id generation / limit checks. */
|
|
14171
14185
|
columnHeaderActionsTemplate = input(null, ...(ngDevMode ? [{ debugName: "columnHeaderActionsTemplate" }] : []));
|
|
14172
|
-
/** Overrides the built-in
|
|
14173
|
-
*
|
|
14174
|
-
*
|
|
14186
|
+
/** Overrides the built-in empty-column placeholder. Receives
|
|
14187
|
+
* `BkKanbanEmptyStateContext`. Takes priority over both `emptyMessage`
|
|
14188
|
+
* and a column's own `emptyMessage`. */
|
|
14175
14189
|
emptyStateTemplate = input(null, ...(ngDevMode ? [{ debugName: "emptyStateTemplate" }] : []));
|
|
14176
|
-
/** Board-wide placeholder text for an empty column, used whenever a
|
|
14177
|
-
* doesn't set its own `
|
|
14178
|
-
* isn't supplied
|
|
14179
|
-
|
|
14180
|
-
/** Whether a column flags an over-limit drop while still dragging
|
|
14181
|
-
* or only once the drop is attempted (`'on-drop'`).
|
|
14182
|
-
* `isLiveOverLimit` for how the two relate to `wipLimitBehavior`. */
|
|
14190
|
+
/** Board-wide placeholder text for an empty column, used whenever a
|
|
14191
|
+
* column doesn't set its own `emptyMessage`. Only read when
|
|
14192
|
+
* `emptyStateTemplate` isn't supplied. */
|
|
14193
|
+
emptyMessage = input('No cards', ...(ngDevMode ? [{ debugName: "emptyMessage" }] : []));
|
|
14194
|
+
/** Whether a column flags an over-limit drop while still dragging
|
|
14195
|
+
* (`'live'`) or only once the drop is attempted (`'on-drop'`). */
|
|
14183
14196
|
dragPreviewMode = input('live', ...(ngDevMode ? [{ debugName: "dragPreviewMode" }] : []));
|
|
14197
|
+
/** Shows a built-in "+" button in every column's header (unless that
|
|
14198
|
+
* column's own `allowAdd` is `false`) when true. It calls the same
|
|
14199
|
+
* `addItem` logic exposed to `columnHeaderActionsTemplate` — with a
|
|
14200
|
+
* default `{ label: 'New item' }` — so out-of-the-box adding works with
|
|
14201
|
+
* zero template configuration. Renders alongside, not instead of,
|
|
14202
|
+
* `columnHeaderActionsTemplate` when both are supplied. */
|
|
14203
|
+
showAddItem = input(false, ...(ngDevMode ? [{ debugName: "showAddItem" }] : []));
|
|
14204
|
+
/** Shows a built-in remove button on every card (unless that card's
|
|
14205
|
+
* column has `allowRemove: false`) when true — board-owned chrome, not
|
|
14206
|
+
* part of `cardTemplate`, so click/drag isolation is guaranteed by the
|
|
14207
|
+
* board itself rather than left to every consumer's own template to get
|
|
14208
|
+
* right. */
|
|
14209
|
+
showRemoveItem = input(false, ...(ngDevMode ? [{ debugName: "showRemoveItem" }] : []));
|
|
14184
14210
|
/* ================= Outputs ================= */
|
|
14185
|
-
|
|
14186
|
-
|
|
14211
|
+
/** Fires after any operation that changes the board. See
|
|
14212
|
+
* `BkKanbanColumnsChangedEvent`'s doc comment for how to tell which
|
|
14213
|
+
* operation happened from the metadata field a returned column carries. */
|
|
14214
|
+
columnsChanged = output();
|
|
14187
14215
|
columnLimitExceeded = output();
|
|
14188
|
-
|
|
14189
|
-
|
|
14216
|
+
itemClicked = output();
|
|
14217
|
+
/** Fires for a cross-column drag before any state changes — only when
|
|
14218
|
+
* `validateMove` is on; see the event's own doc comment
|
|
14219
|
+
* and `onItemDrop`. Must be bound (and resolved) once that's on, or
|
|
14220
|
+
* cross-column drag-and-drop stops having any effect. */
|
|
14221
|
+
itemMoveBetweenColumns = output();
|
|
14190
14222
|
/** Reserved — see `BkKanbanColumnActionsContext.trigger`. Most consumers
|
|
14191
14223
|
* bind a click handler directly on their own header-actions template and
|
|
14192
14224
|
* never reach for this. */
|
|
14193
14225
|
columnActionTriggered = output();
|
|
14194
|
-
/* =================
|
|
14226
|
+
/* ================= Board state ================= */
|
|
14195
14227
|
/**
|
|
14196
|
-
* Bumped after every in-place mutation
|
|
14197
|
-
* `
|
|
14198
|
-
*
|
|
14199
|
-
*
|
|
14200
|
-
*
|
|
14201
|
-
* redraw in the right place without waiting on the host to hand back a
|
|
14202
|
-
* whole new array.
|
|
14228
|
+
* Bumped after every in-place mutation so the template re-reads
|
|
14229
|
+
* `boardColumns()` even though neither `columns()`'s own array identity
|
|
14230
|
+
* nor any column/item's object identity necessarily changed — mutating
|
|
14231
|
+
* fields in place is what lets a drop/add/remove redraw in the right
|
|
14232
|
+
* place without waiting on the host to hand back a whole new array.
|
|
14203
14233
|
*/
|
|
14204
14234
|
structureTick = signal(0, ...(ngDevMode ? [{ debugName: "structureTick" }] : []));
|
|
14205
|
-
/**
|
|
14206
|
-
*
|
|
14207
|
-
*
|
|
14208
|
-
|
|
14209
|
-
grouped = computed(() => {
|
|
14235
|
+
/** What the template actually iterates — a signal that re-evaluates on
|
|
14236
|
+
* every `structureTick` bump, so in-place mutations to `columns()`'s own
|
|
14237
|
+
* items/sortOrder are always picked up. */
|
|
14238
|
+
boardColumns = computed(() => {
|
|
14210
14239
|
this.structureTick();
|
|
14211
|
-
|
|
14212
|
-
|
|
14213
|
-
map.set(column.id, []);
|
|
14214
|
-
for (const card of this.cards()) {
|
|
14215
|
-
const list = map.get(this.resolveColumnId(card));
|
|
14216
|
-
list?.push(card);
|
|
14217
|
-
}
|
|
14218
|
-
for (const list of map.values()) {
|
|
14219
|
-
if (list.some((c) => this.cardOrder(c) !== null)) {
|
|
14220
|
-
list.sort((a, b) => (this.cardOrder(a) ?? 0) - (this.cardOrder(b) ?? 0));
|
|
14221
|
-
}
|
|
14222
|
-
}
|
|
14223
|
-
return map;
|
|
14224
|
-
}, ...(ngDevMode ? [{ debugName: "grouped" }] : []));
|
|
14225
|
-
/** The cards to render for one column, in display order. */
|
|
14226
|
-
cardsFor(columnId) {
|
|
14227
|
-
return this.grouped().get(columnId) ?? [];
|
|
14228
|
-
}
|
|
14229
|
-
resolveColumnId(card) {
|
|
14230
|
-
const field = this.columnIdField();
|
|
14231
|
-
return typeof field === 'function' ? field(card) : card[field];
|
|
14232
|
-
}
|
|
14233
|
-
cardOrder(card) {
|
|
14234
|
-
const value = card['order'];
|
|
14235
|
-
return typeof value === 'number' ? value : null;
|
|
14236
|
-
}
|
|
14240
|
+
return this.columns();
|
|
14241
|
+
}, ...(ngDevMode ? [{ debugName: "boardColumns" }] : []));
|
|
14237
14242
|
resolveTrackBy(card, index) {
|
|
14238
14243
|
const fn = this.trackBy();
|
|
14239
|
-
return fn ? fn(card) :
|
|
14244
|
+
return fn ? fn(card) : card.id;
|
|
14245
|
+
}
|
|
14246
|
+
columnById(id) {
|
|
14247
|
+
return this.boardColumns().find((c) => c.id === id);
|
|
14240
14248
|
}
|
|
14241
14249
|
/* ================= Column-level view state ================= */
|
|
14242
14250
|
/** Column-header count badge, flagged once the column is over its limit —
|
|
@@ -14244,18 +14252,29 @@ class BkKanban {
|
|
|
14244
14252
|
* column get there and `'none'` treats the number as pure information. */
|
|
14245
14253
|
isColumnOverLimit(column) {
|
|
14246
14254
|
return (this.wipLimitBehavior() === 'warn' &&
|
|
14247
|
-
column.
|
|
14248
|
-
|
|
14255
|
+
column.limit != null &&
|
|
14256
|
+
column.items.length > column.limit);
|
|
14249
14257
|
}
|
|
14250
14258
|
/** "3" with no limit, "3 / 5" with one — the header count badge's label. */
|
|
14251
14259
|
columnCountLabel(column) {
|
|
14252
|
-
|
|
14253
|
-
|
|
14260
|
+
return column.limit != null ? `${column.items.length} / ${column.limit}` : `${column.items.length}`;
|
|
14261
|
+
}
|
|
14262
|
+
/** Whether the built-in "+" button should render for this column —
|
|
14263
|
+
* `showAddItem` board-wide, unless this column opted itself out. */
|
|
14264
|
+
showAddButton(column) {
|
|
14265
|
+
return this.showAddItem() && column.allowAdd !== false;
|
|
14266
|
+
}
|
|
14267
|
+
/** Whether the built-in remove button should render for this card's
|
|
14268
|
+
* column — `showRemoveItem` board-wide, unless this column opted
|
|
14269
|
+
* itself out. */
|
|
14270
|
+
showRemoveButton(column) {
|
|
14271
|
+
return this.showRemoveItem() && column.allowRemove !== false;
|
|
14254
14272
|
}
|
|
14255
14273
|
columnActionsContext(column) {
|
|
14256
14274
|
return {
|
|
14257
14275
|
$implicit: column,
|
|
14258
14276
|
column,
|
|
14277
|
+
addItem: (item) => this.addItem(column.id, item),
|
|
14259
14278
|
trigger: () => this.columnActionTriggered.emit({ columnId: column.id })
|
|
14260
14279
|
};
|
|
14261
14280
|
}
|
|
@@ -14263,49 +14282,65 @@ class BkKanban {
|
|
|
14263
14282
|
return { $implicit: column, column };
|
|
14264
14283
|
}
|
|
14265
14284
|
/** Text for the built-in empty-column placeholder: a column's own
|
|
14266
|
-
* `
|
|
14267
|
-
* Only consulted when `emptyStateTemplate` isn't supplied
|
|
14268
|
-
|
|
14269
|
-
|
|
14270
|
-
return column.noDataText ?? this.noDataText();
|
|
14285
|
+
* `emptyMessage` when it sets one, else the board-wide `emptyMessage`
|
|
14286
|
+
* input. Only consulted when `emptyStateTemplate` isn't supplied. */
|
|
14287
|
+
emptyMessageFor(column) {
|
|
14288
|
+
return column.emptyMessage ?? this.emptyMessage();
|
|
14271
14289
|
}
|
|
14272
14290
|
cardContext(card, column, index) {
|
|
14273
|
-
return {
|
|
14291
|
+
return {
|
|
14292
|
+
$implicit: card,
|
|
14293
|
+
card,
|
|
14294
|
+
column,
|
|
14295
|
+
index,
|
|
14296
|
+
removeItem: () => this.removeItem(column.id, card.id)
|
|
14297
|
+
};
|
|
14274
14298
|
}
|
|
14275
|
-
/**
|
|
14299
|
+
/** Whether any card dragging is active at all — false only for
|
|
14300
|
+
* `movementRestriction: 'none'`. Drives both the column body's own
|
|
14301
|
+
* `cdkDropListDisabled` and each card's own `cdkDragDisabled`. */
|
|
14302
|
+
dragActive = computed(() => this.movementRestriction() !== 'none', ...(ngDevMode ? [{ debugName: "dragActive" }] : []));
|
|
14303
|
+
/** Which columns a column's drop list connects to, per `movementRestriction`. */
|
|
14276
14304
|
connectedListIds(columnId) {
|
|
14277
|
-
|
|
14305
|
+
const restriction = this.movementRestriction();
|
|
14306
|
+
if (restriction === 'none' || restriction === 'within')
|
|
14278
14307
|
return [];
|
|
14279
|
-
return this.
|
|
14308
|
+
return this.boardColumns()
|
|
14280
14309
|
.filter((c) => c.id !== columnId)
|
|
14281
14310
|
.map((c) => c.id);
|
|
14282
14311
|
}
|
|
14283
|
-
/** `'
|
|
14284
|
-
* to be switched off at the list level, since a
|
|
14285
|
-
* alone still leaves the list free to sort
|
|
14286
|
-
|
|
14312
|
+
/** `'between'` means *only* moves between columns — reordering
|
|
14313
|
+
* within one has to be switched off at the list level, since a
|
|
14314
|
+
* scoped-out `connectedTo` alone still leaves the list free to sort
|
|
14315
|
+
* itself. `'none'` doesn't need to appear here too: `cdkDropListDisabled`
|
|
14316
|
+
* (bound to `dragActive`) already switches the whole list off, so whether
|
|
14317
|
+
* sorting specifically is "disabled" on top of that is moot. */
|
|
14318
|
+
sortingDisabled = computed(() => this.movementRestriction() === 'between', ...(ngDevMode ? [{ debugName: "sortingDisabled" }] : []));
|
|
14287
14319
|
/* ================= Column-level drag (reordering columns) ================= */
|
|
14288
14320
|
/**
|
|
14289
|
-
* `columns` is reordered in place — same trick as
|
|
14321
|
+
* `columns` is reordered in place — same trick as items (see the
|
|
14290
14322
|
* component doc): the array reference the host handed in is mutated
|
|
14291
|
-
* directly (`moveItemInArray`) rather than swapped for a new one
|
|
14292
|
-
*
|
|
14293
|
-
*
|
|
14294
|
-
*
|
|
14295
|
-
*
|
|
14296
|
-
*
|
|
14323
|
+
* directly (`moveItemInArray`) rather than swapped for a new one.
|
|
14324
|
+
* `sortOrder` is then renumbered for the whole array, and — unlike every
|
|
14325
|
+
* other operation, which reports only the column(s) actually affected —
|
|
14326
|
+
* *every* column in the board is reported here, in its resulting order,
|
|
14327
|
+
* shaped per `columnReorderPayload`: consumers persisting a full column
|
|
14328
|
+
* order need to see the whole list, not just the entries that happened to
|
|
14329
|
+
* shift.
|
|
14297
14330
|
*/
|
|
14298
14331
|
onColumnDrop(event) {
|
|
14299
|
-
if (!this.
|
|
14332
|
+
if (!this.dragColumn() || event.previousIndex === event.currentIndex)
|
|
14300
14333
|
return;
|
|
14301
14334
|
const columns = this.columns();
|
|
14302
|
-
const moved = columns[event.previousIndex];
|
|
14303
14335
|
moveItemInArray(columns, event.previousIndex, event.currentIndex);
|
|
14304
|
-
|
|
14305
|
-
|
|
14306
|
-
fromIndex: event.previousIndex,
|
|
14307
|
-
toIndex: event.currentIndex
|
|
14336
|
+
columns.forEach((c, i) => {
|
|
14337
|
+
c.sortOrder = i + 1;
|
|
14308
14338
|
});
|
|
14339
|
+
this.structureTick.update((n) => n + 1);
|
|
14340
|
+
const payload = this.columnReorderPayload() === 'summary'
|
|
14341
|
+
? columns.map((c) => ({ id: c.id, sortOrder: c.sortOrder }))
|
|
14342
|
+
: columns.map((c) => ({ ...c }));
|
|
14343
|
+
this.columnsChanged.emit({ columns: payload });
|
|
14309
14344
|
}
|
|
14310
14345
|
/* ================= Drag state (for the live WIP preview) ================= */
|
|
14311
14346
|
dragSourceColumnId = signal(null, ...(ngDevMode ? [{ debugName: "dragSourceColumnId" }] : []));
|
|
@@ -14318,19 +14353,19 @@ class BkKanban {
|
|
|
14318
14353
|
this.hoveredOverLimitColumnId.set(null);
|
|
14319
14354
|
}
|
|
14320
14355
|
/**
|
|
14321
|
-
* Live WIP-limit preview
|
|
14322
|
-
*
|
|
14323
|
-
*
|
|
14324
|
-
*
|
|
14325
|
-
*
|
|
14326
|
-
*
|
|
14356
|
+
* Live WIP-limit preview: as the dragged item enters a column, work out
|
|
14357
|
+
* whether *landing* here would exceed its `limit` and flag it immediately
|
|
14358
|
+
* — before release, and independent of `wipLimitBehavior`, which only
|
|
14359
|
+
* governs what happens once the item is actually dropped. An item
|
|
14360
|
+
* re-entering its own source column doesn't grow that column's count, so
|
|
14361
|
+
* only a genuine cross-column entry counts as +1.
|
|
14327
14362
|
*/
|
|
14328
14363
|
onListEntered(event, column) {
|
|
14329
|
-
if (this.dragPreviewMode() !== 'live' || column.
|
|
14364
|
+
if (this.dragPreviewMode() !== 'live' || column.limit == null)
|
|
14330
14365
|
return;
|
|
14331
14366
|
const enteringFromElsewhere = this.dragSourceColumnId() !== column.id;
|
|
14332
14367
|
const wouldBeCount = event.container.data.length + (enteringFromElsewhere ? 1 : 0);
|
|
14333
|
-
this.hoveredOverLimitColumnId.set(wouldBeCount > column.
|
|
14368
|
+
this.hoveredOverLimitColumnId.set(wouldBeCount > column.limit ? column.id : null);
|
|
14334
14369
|
}
|
|
14335
14370
|
onListExited(column) {
|
|
14336
14371
|
if (this.hoveredOverLimitColumnId() === column.id)
|
|
@@ -14339,87 +14374,233 @@ class BkKanban {
|
|
|
14339
14374
|
isLiveOverLimit(column) {
|
|
14340
14375
|
return this.hoveredOverLimitColumnId() === column.id;
|
|
14341
14376
|
}
|
|
14342
|
-
/* =================
|
|
14343
|
-
|
|
14377
|
+
/* ================= Item drop handling ================= */
|
|
14378
|
+
renumber(items) {
|
|
14379
|
+
items.forEach((item, index) => {
|
|
14380
|
+
item.sortOrder = index + 1;
|
|
14381
|
+
});
|
|
14382
|
+
}
|
|
14383
|
+
/**
|
|
14384
|
+
* Same-column reorder commits immediately (nothing to validate — the
|
|
14385
|
+
* column's own membership never changes) and reports the one affected
|
|
14386
|
+
* column with its complete, re-ordered `items`. A genuine cross-column
|
|
14387
|
+
* drop is a different story: see the second half of this method and
|
|
14388
|
+
* `itemMoveBetweenColumns`'s own doc comment for why it does *not*
|
|
14389
|
+
* mutate anything here.
|
|
14390
|
+
*/
|
|
14391
|
+
onItemDrop(event, column) {
|
|
14344
14392
|
this.dragSourceColumnId.set(null);
|
|
14345
14393
|
this.hoveredOverLimitColumnId.set(null);
|
|
14346
|
-
if (!this.
|
|
14394
|
+
if (!this.dragActive())
|
|
14347
14395
|
return;
|
|
14348
14396
|
const fromColumnId = event.previousContainer.id;
|
|
14349
14397
|
const toColumnId = event.container.id;
|
|
14350
|
-
const
|
|
14398
|
+
const item = event.item.data;
|
|
14351
14399
|
if (fromColumnId === toColumnId) {
|
|
14352
14400
|
if (event.previousIndex === event.currentIndex)
|
|
14353
14401
|
return;
|
|
14354
|
-
const
|
|
14355
|
-
moveItemInArray(
|
|
14356
|
-
this.renumber(
|
|
14402
|
+
const items = column.items;
|
|
14403
|
+
moveItemInArray(items, event.previousIndex, event.currentIndex);
|
|
14404
|
+
this.renumber(items);
|
|
14357
14405
|
this.structureTick.update((n) => n + 1);
|
|
14358
|
-
this.
|
|
14406
|
+
this.columnsChanged.emit({ columns: [{ ...column }] });
|
|
14359
14407
|
return;
|
|
14360
14408
|
}
|
|
14361
|
-
const
|
|
14362
|
-
const
|
|
14363
|
-
|
|
14364
|
-
|
|
14409
|
+
const fromColumn = this.columnById(fromColumnId);
|
|
14410
|
+
const toColumn = this.columnById(toColumnId) ?? column;
|
|
14411
|
+
if (!fromColumn)
|
|
14412
|
+
return;
|
|
14413
|
+
const attemptedCount = toColumn.items.length + 1;
|
|
14414
|
+
const limit = toColumn.limit;
|
|
14415
|
+
const wouldExceed = limit != null && attemptedCount > limit;
|
|
14365
14416
|
if (wouldExceed) {
|
|
14366
|
-
this.columnLimitExceeded.emit({ columnId: toColumnId,
|
|
14417
|
+
this.columnLimitExceeded.emit({ columnId: toColumnId, limit: limit, attemptedCount });
|
|
14367
14418
|
}
|
|
14368
14419
|
if (wouldExceed && this.wipLimitBehavior() === 'block') {
|
|
14369
|
-
// Rejected
|
|
14370
|
-
// still exactly where `
|
|
14371
|
-
// cancelled CDK drag shows
|
|
14372
|
-
return;
|
|
14373
|
-
}
|
|
14374
|
-
const
|
|
14375
|
-
|
|
14420
|
+
// Rejected before validation is even asked for: nothing below runs,
|
|
14421
|
+
// so the next render finds the item still exactly where `columns()`
|
|
14422
|
+
// says it is — the same "snap back" any cancelled CDK drag shows.
|
|
14423
|
+
return;
|
|
14424
|
+
}
|
|
14425
|
+
const targetIndex = event.currentIndex;
|
|
14426
|
+
// Opt-in gate: off by default, a cross-column drop commits right here,
|
|
14427
|
+
// same as a same-column reorder — no event, nothing to wait for.
|
|
14428
|
+
if (!this.validateMove()) {
|
|
14429
|
+
this.commitCrossColumnMove(fromColumn, toColumn, item, targetIndex);
|
|
14430
|
+
return;
|
|
14431
|
+
}
|
|
14432
|
+
// ---- Pre-drop validation (§5-§9, §23), only once opted into ----
|
|
14433
|
+
//
|
|
14434
|
+
// Everything needed to describe the move is worked out now, from
|
|
14435
|
+
// `event`'s own indices — CDK reports `currentIndex` as the intended
|
|
14436
|
+
// position within the destination list purely from the drag's visual
|
|
14437
|
+
// state, so it's already meaningful even though `toColumn.items` itself
|
|
14438
|
+
// hasn't been touched yet. Nothing below this point mutates board state
|
|
14439
|
+
// — that only happens inside `commitCrossColumnMove`, and only once
|
|
14440
|
+
// `resolve` is actually called. Until then (and forever, if it's never
|
|
14441
|
+
// called) `fromColumn`/`toColumn` are untouched, so the item simply
|
|
14442
|
+
// stays rendered exactly where `columns()` already says it is — "restore
|
|
14443
|
+
// to original position" on failure is this doing nothing, not a
|
|
14444
|
+
// separate undo step.
|
|
14445
|
+
const targetItem = toColumn.items[targetIndex] ?? toColumn.items[targetIndex - 1] ?? null;
|
|
14446
|
+
let settled = false;
|
|
14447
|
+
this.itemMoveBetweenColumns.emit({
|
|
14448
|
+
item,
|
|
14449
|
+
sourceColumn: { ...fromColumn },
|
|
14450
|
+
destinationColumn: { ...toColumn },
|
|
14451
|
+
targetItem,
|
|
14452
|
+
targetIndex,
|
|
14453
|
+
resolve: (result) => {
|
|
14454
|
+
if (settled)
|
|
14455
|
+
return;
|
|
14456
|
+
settled = true;
|
|
14457
|
+
if (result?.success) {
|
|
14458
|
+
this.commitCrossColumnMove(fromColumn, toColumn, item, targetIndex);
|
|
14459
|
+
}
|
|
14460
|
+
// Failure: intentionally nothing else to do — see comment above.
|
|
14461
|
+
}
|
|
14462
|
+
});
|
|
14463
|
+
}
|
|
14464
|
+
/** The actual cross-column mutation, run only once `resolve({ success:
|
|
14465
|
+
* true })` comes back from `itemMoveBetweenColumns` — see `onItemDrop`. */
|
|
14466
|
+
commitCrossColumnMove(fromColumn, toColumn, item, targetIndex) {
|
|
14467
|
+
const fromIndex = fromColumn.items.indexOf(item);
|
|
14376
14468
|
if (fromIndex < 0)
|
|
14377
|
-
return; // Column and
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
this.renumber(
|
|
14469
|
+
return; // Column and item disagree about where it lives; leave state alone.
|
|
14470
|
+
fromColumn.items.splice(fromIndex, 1);
|
|
14471
|
+
const insertIndex = Math.min(targetIndex, toColumn.items.length);
|
|
14472
|
+
toColumn.items.splice(insertIndex, 0, item);
|
|
14473
|
+
item.parentId = toColumn.id;
|
|
14474
|
+
this.renumber(fromColumn.items);
|
|
14475
|
+
this.renumber(toColumn.items);
|
|
14476
|
+
this.structureTick.update((n) => n + 1);
|
|
14477
|
+
this.columnsChanged.emit({
|
|
14478
|
+
columns: [
|
|
14479
|
+
{ ...fromColumn, sentChildId: item.id },
|
|
14480
|
+
{ ...toColumn, receivedChildId: item.id }
|
|
14481
|
+
]
|
|
14482
|
+
});
|
|
14483
|
+
}
|
|
14484
|
+
/* ================= Add / remove ================= */
|
|
14485
|
+
generateId() {
|
|
14486
|
+
return typeof crypto !== 'undefined' && crypto.randomUUID
|
|
14487
|
+
? crypto.randomUUID()
|
|
14488
|
+
: `item-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
14489
|
+
}
|
|
14490
|
+
/** Adds a new item to a column — the logic backing both the built-in "+"
|
|
14491
|
+
* button (`showAddItem`) and `BkKanbanColumnActionsContext.addItem`
|
|
14492
|
+
* for a fully custom header template. Supply at least `label`; an `id`
|
|
14493
|
+
* given is used as-is, otherwise one is generated. Respects
|
|
14494
|
+
* `wipLimitBehavior` exactly like a cross-column drop does, and does
|
|
14495
|
+
* nothing at all for a column with `allowAdd: false` or an empty/missing
|
|
14496
|
+
* `label` — see §11. */
|
|
14497
|
+
addItem(columnId, data) {
|
|
14498
|
+
const column = this.columnById(columnId);
|
|
14499
|
+
if (!column || column.allowAdd === false)
|
|
14500
|
+
return;
|
|
14501
|
+
if (!data?.label || !data.label.trim())
|
|
14502
|
+
return;
|
|
14503
|
+
const attemptedCount = column.items.length + 1;
|
|
14504
|
+
const limit = column.limit;
|
|
14505
|
+
const wouldExceed = limit != null && attemptedCount > limit;
|
|
14506
|
+
if (wouldExceed) {
|
|
14507
|
+
this.columnLimitExceeded.emit({ columnId, limit: limit, attemptedCount });
|
|
14508
|
+
}
|
|
14509
|
+
if (wouldExceed && this.wipLimitBehavior() === 'block')
|
|
14510
|
+
return;
|
|
14511
|
+
const id = data.id ?? this.generateId();
|
|
14512
|
+
const item = { ...data, id, sortOrder: column.items.length + 1, parentId: columnId };
|
|
14513
|
+
column.items.push(item);
|
|
14383
14514
|
this.structureTick.update((n) => n + 1);
|
|
14384
|
-
this.
|
|
14385
|
-
|
|
14386
|
-
setColumnId(card, columnId) {
|
|
14387
|
-
const field = this.columnIdField();
|
|
14388
|
-
// A resolver function has nowhere to write back to — the host owns that
|
|
14389
|
-
// mapping and is expected to apply the move from `cardMoved` itself.
|
|
14390
|
-
if (typeof field === 'string')
|
|
14391
|
-
card[field] = columnId;
|
|
14392
|
-
}
|
|
14393
|
-
renumber(list) {
|
|
14394
|
-
list.forEach((card, index) => {
|
|
14395
|
-
card['order'] = index;
|
|
14515
|
+
this.columnsChanged.emit({
|
|
14516
|
+
columns: [{ ...column, newAddedItemId: id, newItemCreated: true }]
|
|
14396
14517
|
});
|
|
14397
14518
|
}
|
|
14398
|
-
|
|
14519
|
+
/** Removes an item from a column — the logic backing both the built-in
|
|
14520
|
+
* remove button (`showRemoveItem`) and `BkKanbanCardContext.removeItem`
|
|
14521
|
+
* for a fully custom card body. Does nothing for a column with
|
|
14522
|
+
* `allowRemove: false`, or if the item isn't actually there. */
|
|
14523
|
+
removeItem(columnId, itemId) {
|
|
14524
|
+
const column = this.columnById(columnId);
|
|
14525
|
+
if (!column || column.allowRemove === false)
|
|
14526
|
+
return;
|
|
14527
|
+
const index = column.items.findIndex((i) => i.id === itemId);
|
|
14528
|
+
if (index < 0)
|
|
14529
|
+
return;
|
|
14530
|
+
column.items.splice(index, 1);
|
|
14531
|
+
this.renumber(column.items);
|
|
14532
|
+
this.structureTick.update((n) => n + 1);
|
|
14533
|
+
this.columnsChanged.emit({ columns: [{ ...column, removedItemId: itemId }] });
|
|
14534
|
+
}
|
|
14535
|
+
onAddButtonClick(column) {
|
|
14536
|
+
this.addItem(column.id, { label: 'New item' });
|
|
14537
|
+
}
|
|
14538
|
+
/** The remove button's own click handler. It's a `bk-icon-button`, so its
|
|
14539
|
+
* own `(clicked)` output only ever carries a boolean, not the originating
|
|
14540
|
+
* event — isolation happens entirely in the template instead, via native
|
|
14541
|
+
* `mousedown`/`pointerdown`/`click` listeners bound directly on the
|
|
14542
|
+
* `<bk-icon-button>` host: `mousedown`/`pointerdown` are stopped before
|
|
14543
|
+
* CDK's drag-start detection (which listens on the card itself, an
|
|
14544
|
+
* ancestor of this button) ever sees the press, and stopping `click` too
|
|
14545
|
+
* keeps it from also reaching `onItemClick` on the card underneath. See
|
|
14546
|
+
* §18/§12 of the spec this shipped against. */
|
|
14547
|
+
onRemoveButtonClick(column, card) {
|
|
14548
|
+
this.removeItem(column.id, card.id);
|
|
14549
|
+
}
|
|
14550
|
+
/* ================= Remove button hover (icon swap) =================
|
|
14551
|
+
*
|
|
14552
|
+
* `bk-icon-button`'s own stylesheet defines no real `:hover` state (only
|
|
14553
|
+
* `active:`/`focus-visible:`) — the pattern this codebase already uses
|
|
14554
|
+
* instead, for the same "×" affordance elsewhere (see the fleet module's
|
|
14555
|
+
* own close button), is to swap `variant` (from `'none'`, transparent, to
|
|
14556
|
+
* `'primary'`, solid) *and* the icon asset itself (a black icon in the
|
|
14557
|
+
* `'none'` state needs to become the white variant once the background
|
|
14558
|
+
* goes solid) on `(hovered)`, rather than reaching for a CSS override.
|
|
14559
|
+
*
|
|
14560
|
+
* Tracked per item id, not a single shared boolean: there's one remove
|
|
14561
|
+
* button per card, all sharing this same board instance, so "which one"
|
|
14562
|
+
* has to be part of the state. The add button stays a plain `bk-button`
|
|
14563
|
+
* (see kanban.html) and doesn't need this.
|
|
14564
|
+
*/
|
|
14565
|
+
hoveredRemoveItemId = signal(null, ...(ngDevMode ? [{ debugName: "hoveredRemoveItemId" }] : []));
|
|
14566
|
+
removeButtonVariant(card) {
|
|
14567
|
+
return this.hoveredRemoveItemId() === card.id ? 'primary' : 'none';
|
|
14568
|
+
}
|
|
14569
|
+
removeButtonIcon(card) {
|
|
14570
|
+
return this.hoveredRemoveItemId() === card.id
|
|
14571
|
+
? '../../../../assets/images/icons/global/popup-cross-white.svg'
|
|
14572
|
+
: '../../../../assets/images/icons/global/popup-cross.svg';
|
|
14573
|
+
}
|
|
14574
|
+
onRemoveButtonHover(card, hovered) {
|
|
14575
|
+
this.hoveredRemoveItemId.set(hovered ? card.id : null);
|
|
14576
|
+
}
|
|
14577
|
+
/* ================= Click handling ================= */
|
|
14399
14578
|
/**
|
|
14400
14579
|
* One listener per card resolves both the whole-card click and every
|
|
14401
14580
|
* `[bkKanbanClickTarget]` region: `closest()` from the actual event target
|
|
14402
14581
|
* finds the nearest marked ancestor within this card, or there isn't one
|
|
14403
14582
|
* and it's a plain card click. Either way this fires exactly once — never
|
|
14404
|
-
* once for the region and again for the card underneath it.
|
|
14583
|
+
* once for the region and again for the card underneath it. The remove
|
|
14584
|
+
* button (when shown) stops its own click from ever reaching here — see
|
|
14585
|
+
* `onRemoveButtonClick`.
|
|
14405
14586
|
*/
|
|
14406
|
-
|
|
14587
|
+
onItemClick(event, card) {
|
|
14407
14588
|
const wrapper = event.currentTarget;
|
|
14408
14589
|
const marked = event.target?.closest('[data-bk-kanban-click-target]');
|
|
14409
14590
|
const target = marked && wrapper.contains(marked)
|
|
14410
14591
|
? (marked.dataset['bkKanbanClickTarget'] ?? 'card')
|
|
14411
14592
|
: 'card';
|
|
14412
|
-
this.
|
|
14593
|
+
this.itemClicked.emit({ item: card, target });
|
|
14413
14594
|
}
|
|
14414
14595
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkKanban, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14415
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkKanban, isStandalone: true, selector: "bk-kanban", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, cards: { classPropertyName: "cards", publicName: "cards", isSignal: true, isRequired: true, transformFunction: null }, cardTemplate: { classPropertyName: "cardTemplate", publicName: "cardTemplate", isSignal: true, isRequired: true, transformFunction: null }, columnIdField: { classPropertyName: "columnIdField", publicName: "columnIdField", isSignal: true, isRequired: false, transformFunction: null }, dragEnabled: { classPropertyName: "dragEnabled", publicName: "dragEnabled", isSignal: true, isRequired: false, transformFunction: null }, dragScope: { classPropertyName: "dragScope", publicName: "dragScope", isSignal: true, isRequired: false, transformFunction: null }, wipLimitBehavior: { classPropertyName: "wipLimitBehavior", publicName: "wipLimitBehavior", isSignal: true, isRequired: false, transformFunction: null }, columnDragEnabled: { classPropertyName: "columnDragEnabled", publicName: "columnDragEnabled", isSignal: true, isRequired: false, transformFunction: null }, columnScrollMode: { classPropertyName: "columnScrollMode", publicName: "columnScrollMode", isSignal: true, isRequired: false, transformFunction: null }, boardClass: { classPropertyName: "boardClass", publicName: "boardClass", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null }, columnHeaderActionsTemplate: { classPropertyName: "columnHeaderActionsTemplate", publicName: "columnHeaderActionsTemplate", isSignal: true, isRequired: false, transformFunction: null }, emptyStateTemplate: { classPropertyName: "emptyStateTemplate", publicName: "emptyStateTemplate", isSignal: true, isRequired: false, transformFunction: null }, noDataText: { classPropertyName: "noDataText", publicName: "noDataText", isSignal: true, isRequired: false, transformFunction: null }, dragPreviewMode: { classPropertyName: "dragPreviewMode", publicName: "dragPreviewMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cardMoved: "cardMoved", cardClicked: "cardClicked", columnLimitExceeded: "columnLimitExceeded", columnMoved: "columnMoved", columnActionTriggered: "columnActionTriggered" }, host: { classAttribute: "bk-kanban-host" }, exportAs: ["bkKanban"], ngImport: i0, template: "<div class=\"bk-kanban-board\" [class]=\"boardClass()\">\r\n <div\r\n class=\"bk-kanban\"\r\n [class.bk-kanban--scroll-wrapper]=\"columnScrollMode() === 'wrapper'\"\r\n cdkDropList\r\n cdkDropListOrientation=\"horizontal\"\r\n [cdkDropListData]=\"columns()\"\r\n [cdkDropListDisabled]=\"!columnDragEnabled()\"\r\n (cdkDropListDropped)=\"onColumnDrop($event)\"\r\n >\r\n @for (column of columns(); track column.id) {\r\n <div\r\n class=\"bk-kanban-column\"\r\n cdkDrag\r\n [cdkDragData]=\"column\"\r\n [cdkDragDisabled]=\"!columnDragEnabled()\"\r\n [class]=\"column.columnClass\"\r\n [class.bk-kanban-column-warn]=\"isColumnOverLimit(column)\"\r\n [class.bk-kanban-column-live-over]=\"isLiveOverLimit(column)\"\r\n >\r\n <div class=\"bk-kanban-column-header\" cdkDragHandle [class]=\"column.colHeaderClass\">\r\n <span class=\"bk-kanban-column-title\">{{ column.label }}</span>\r\n <bk-badge\r\n class=\"bk-kanban-column-count\"\r\n [label]=\"columnCountLabel(column)\"\r\n size=\"sm\"\r\n [color]=\"isColumnOverLimit(column) || isLiveOverLimit(column) ? 'Warning' : 'Gray'\"\r\n ></bk-badge>\r\n\r\n @if (columnHeaderActionsTemplate(); as actionsTpl) {\r\n <div class=\"bk-kanban-column-actions\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionsTpl\"\r\n [ngTemplateOutletContext]=\"columnActionsContext(column)\"\r\n ></ng-container>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"bk-kanban-column-body\"\r\n cdkDropList\r\n [id]=\"column.id\"\r\n [cdkDropListData]=\"cardsFor(column.id)\"\r\n [cdkDropListConnectedTo]=\"connectedListIds(column.id)\"\r\n [cdkDropListSortingDisabled]=\"sortingDisabled()\"\r\n [cdkDropListDisabled]=\"!dragEnabled()\"\r\n (cdkDropListDropped)=\"onDrop($event, column)\"\r\n (cdkDropListEntered)=\"onListEntered($event, column)\"\r\n (cdkDropListExited)=\"onListExited(column)\"\r\n >\r\n @if (cardsFor(column.id).length === 0) {\r\n <div class=\"bk-kanban-empty\">\r\n @if (emptyStateTemplate(); as emptyTpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"emptyTpl\"\r\n [ngTemplateOutletContext]=\"emptyStateContext(column)\"\r\n ></ng-container>\r\n } @else {\r\n <span class=\"bk-kanban-empty-text\">{{ emptyStateText(column) }}</span>\r\n }\r\n </div>\r\n }\r\n\r\n @for (card of cardsFor(column.id); track resolveTrackBy(card, $index); let i = $index) {\r\n <div\r\n class=\"bk-kanban-card\"\r\n cdkDrag\r\n [cdkDragData]=\"card\"\r\n [cdkDragDisabled]=\"!dragEnabled()\"\r\n (cdkDragStarted)=\"onDragStarted(column)\"\r\n (cdkDragEnded)=\"onDragEnded()\"\r\n (click)=\"onCardClick($event, card)\"\r\n >\r\n <ng-container\r\n [ngTemplateOutlet]=\"cardTemplate()\"\r\n [ngTemplateOutletContext]=\"cardContext(card, column, i)\"\r\n ></ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n", styles: [".bk-kanban-host,.bk-kanban-board{display:block;height:100%}.bk-kanban{@apply flex items-stretch gap-4 w-full h-full overflow-x-auto;scroll-snap-type:x proximity}.bk-kanban--scroll-wrapper{align-items:flex-start;overflow-y:auto}.bk-kanban--scroll-wrapper .bk-kanban-column-body{overflow-y:visible}.bk-kanban-column{@apply flex flex-col shrink-0 w-[300px] max-w-[85vw] rounded-xl border border-[#EBEDF3] overflow-hidden;background:var(--bk-kanban-column-bg, #f9fafa);scroll-snap-align:start}.bk-kanban-column-header{@apply flex items-center justify-between gap-2 px-3 py-2.5;background:var(--bk-kanban-header-bg, #f9fafa);border-bottom:1px solid #ebedf3}.bk-kanban-column:not(.cdk-drag-disabled)>.bk-kanban-column-header{cursor:grab}.bk-kanban-column.cdk-drag-dragging>.bk-kanban-column-header{cursor:grabbing}.bk-kanban-column-title{@apply text-[13px] font-semibold text-[#15191E] truncate;}.bk-kanban-column-count{@apply shrink-0;}.bk-kanban-column-actions{@apply shrink-0 flex items-center;}.bk-kanban-column-body{@apply flex-1 flex flex-col gap-2 p-2.5 overflow-y-auto;min-height:96px}.bk-kanban-empty{@apply flex flex-1 flex-col items-center justify-center py-6 text-center;}.bk-kanban-empty-text{@apply text-xs font-medium text-[#78829D];}.bk-kanban-card{@apply bg-white rounded-lg border border-[#EBEDF3] p-3 cursor-pointer;box-shadow:0 1px 2px #1018280a;transition:box-shadow .15s ease,border-color .15s ease}.bk-kanban-card:hover{@apply border-[#C4CADA];box-shadow:0 2px 6px #10182814}.bk-kanban-card.cdk-drag-dragging{transition:none}.bk-kanban-column-warn{@apply border-amber-300 bg-amber-50;}.bk-kanban-column-warn .bk-kanban-column-header{@apply bg-amber-50 border-amber-200;}.bk-kanban-column-live-over{@apply border-amber-400 bg-amber-50;outline:2px dashed theme(\"colors.amber.400\");outline-offset:-2px}.bk-kanban-column-live-over .bk-kanban-column-header{@apply bg-amber-50 border-amber-200;}.bk-kanban-card.cdk-drag-preview{@apply rounded-lg;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;cursor:grabbing}.bk-kanban-card.cdk-drag-placeholder{@apply border-dashed border-[#C4CADA] bg-[#F1F2F4];box-shadow:none;opacity:.6}.bk-kanban-column-body.cdk-drop-list-dragging .bk-kanban-card:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.bk-kanban-column.cdk-drag-preview{box-shadow:0 8px 10px -6px #0003,0 20px 25px -5px #00000024}.bk-kanban-column.cdk-drag-placeholder{@apply border-dashed border-[#C4CADA] bg-[#F1F2F4];box-shadow:none;opacity:.5}.bk-kanban-column.cdk-drag-placeholder .bk-kanban-column-header,.bk-kanban-column.cdk-drag-placeholder .bk-kanban-column-body{visibility:hidden}.bk-kanban.cdk-drop-list-dragging .bk-kanban-column:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}@media (max-width: 640px){.bk-kanban-column{width:86vw}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i1$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i1$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: BkBadge, selector: "bk-badge", inputs: ["label", "variant", "color", "size", "dot", "removable", "customClass", "customBg", "customBorder", "customText"], outputs: ["clicked"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
14596
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: BkKanban, isStandalone: true, selector: "bk-kanban", inputs: { columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: true, transformFunction: null }, cardTemplate: { classPropertyName: "cardTemplate", publicName: "cardTemplate", isSignal: true, isRequired: true, transformFunction: null }, movementRestriction: { classPropertyName: "movementRestriction", publicName: "movementRestriction", isSignal: true, isRequired: false, transformFunction: null }, wipLimitBehavior: { classPropertyName: "wipLimitBehavior", publicName: "wipLimitBehavior", isSignal: true, isRequired: false, transformFunction: null }, dragColumn: { classPropertyName: "dragColumn", publicName: "dragColumn", isSignal: true, isRequired: false, transformFunction: null }, columnReorderPayload: { classPropertyName: "columnReorderPayload", publicName: "columnReorderPayload", isSignal: true, isRequired: false, transformFunction: null }, validateMove: { classPropertyName: "validateMove", publicName: "validateMove", isSignal: true, isRequired: false, transformFunction: null }, columnScrollMode: { classPropertyName: "columnScrollMode", publicName: "columnScrollMode", isSignal: true, isRequired: false, transformFunction: null }, boardClass: { classPropertyName: "boardClass", publicName: "boardClass", isSignal: true, isRequired: false, transformFunction: null }, trackBy: { classPropertyName: "trackBy", publicName: "trackBy", isSignal: true, isRequired: false, transformFunction: null }, columnHeaderActionsTemplate: { classPropertyName: "columnHeaderActionsTemplate", publicName: "columnHeaderActionsTemplate", isSignal: true, isRequired: false, transformFunction: null }, emptyStateTemplate: { classPropertyName: "emptyStateTemplate", publicName: "emptyStateTemplate", isSignal: true, isRequired: false, transformFunction: null }, emptyMessage: { classPropertyName: "emptyMessage", publicName: "emptyMessage", isSignal: true, isRequired: false, transformFunction: null }, dragPreviewMode: { classPropertyName: "dragPreviewMode", publicName: "dragPreviewMode", isSignal: true, isRequired: false, transformFunction: null }, showAddItem: { classPropertyName: "showAddItem", publicName: "showAddItem", isSignal: true, isRequired: false, transformFunction: null }, showRemoveItem: { classPropertyName: "showRemoveItem", publicName: "showRemoveItem", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { columnsChanged: "columnsChanged", columnLimitExceeded: "columnLimitExceeded", itemClicked: "itemClicked", itemMoveBetweenColumns: "itemMoveBetweenColumns", columnActionTriggered: "columnActionTriggered" }, host: { classAttribute: "bk-kanban-host" }, exportAs: ["bkKanban"], ngImport: i0, template: "<div class=\"bk-kanban-board\" [class]=\"boardClass()\">\r\n <div\r\n class=\"bk-kanban\"\r\n [class.bk-kanban--scroll-wrapper]=\"columnScrollMode() === 'wrapper'\"\r\n cdkDropList\r\n cdkDropListOrientation=\"horizontal\"\r\n [cdkDropListData]=\"boardColumns()\"\r\n [cdkDropListDisabled]=\"!dragColumn()\"\r\n (cdkDropListDropped)=\"onColumnDrop($event)\"\r\n >\r\n @for (column of boardColumns(); track column.id) {\r\n <div\r\n class=\"bk-kanban-column\"\r\n cdkDrag\r\n [cdkDragData]=\"column\"\r\n [cdkDragDisabled]=\"!dragColumn()\"\r\n [class]=\"column.columnClass\"\r\n [class.bk-kanban-column-warn]=\"isColumnOverLimit(column)\"\r\n [class.bk-kanban-column-live-over]=\"isLiveOverLimit(column)\"\r\n >\r\n <div class=\"bk-kanban-column-header\" cdkDragHandle [class]=\"column.colHeaderClass\">\r\n <span class=\"bk-kanban-column-title\">{{ column.label }}</span>\r\n <bk-badge\r\n class=\"bk-kanban-column-count\"\r\n [label]=\"columnCountLabel(column)\"\r\n size=\"sm\"\r\n [color]=\"isColumnOverLimit(column) || isLiveOverLimit(column) ? 'Warning' : 'Gray'\"\r\n ></bk-badge>\r\n\r\n @if (columnHeaderActionsTemplate(); as actionsTpl) {\r\n <div class=\"bk-kanban-column-actions\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionsTpl\"\r\n [ngTemplateOutletContext]=\"columnActionsContext(column)\"\r\n ></ng-container>\r\n </div>\r\n }\r\n @if (showAddButton(column)) {\r\n <bk-button\r\n class=\"bk-kanban-add-btn\"\r\n variant=\"primary\"\r\n size=\"xxsm\"\r\n [shadow]=\"false\"\r\n label=\"+\"\r\n [buttonClass]=\"'size-6 hover:bg-[#242424]'\"\r\n [attr.aria-label]=\"'Add item to ' + column.label\"\r\n (clicked)=\"onAddButtonClick(column)\"\r\n ></bk-button>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"bk-kanban-column-body\"\r\n cdkDropList\r\n [id]=\"column.id\"\r\n [cdkDropListData]=\"column.items\"\r\n [cdkDropListConnectedTo]=\"connectedListIds(column.id)\"\r\n [cdkDropListSortingDisabled]=\"sortingDisabled()\"\r\n [cdkDropListDisabled]=\"!dragActive()\"\r\n (cdkDropListDropped)=\"onItemDrop($event, column)\"\r\n (cdkDropListEntered)=\"onListEntered($event, column)\"\r\n (cdkDropListExited)=\"onListExited(column)\"\r\n >\r\n @if (column.items.length === 0) {\r\n <div class=\"bk-kanban-empty\">\r\n @if (emptyStateTemplate(); as emptyTpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"emptyTpl\"\r\n [ngTemplateOutletContext]=\"emptyStateContext(column)\"\r\n ></ng-container>\r\n } @else {\r\n <span class=\"bk-kanban-empty-text\">{{ emptyMessageFor(column) }}</span>\r\n }\r\n </div>\r\n }\r\n\r\n @for (card of column.items; track resolveTrackBy(card, $index); let i = $index) {\r\n <div\r\n class=\"bk-kanban-card\"\r\n cdkDrag\r\n [cdkDragData]=\"card\"\r\n [cdkDragDisabled]=\"!dragActive()\"\r\n (cdkDragStarted)=\"onDragStarted(column)\"\r\n (cdkDragEnded)=\"onDragEnded()\"\r\n (click)=\"onItemClick($event, card)\"\r\n >\r\n @if (showRemoveButton(column)) {\r\n <bk-icon-button\r\n class=\"bk-kanban-remove-btn\"\r\n [icon]=\"removeButtonIcon(card)\"\r\n [alt]=\"'Remove ' + card.label\"\r\n [variant]=\"removeButtonVariant(card)\"\r\n size=\"xxxsm\"\r\n (hovered)=\"onRemoveButtonHover(card, $event)\"\r\n (mousedown)=\"$event.stopPropagation()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (clicked)=\"onRemoveButtonClick(column, card)\"\r\n ></bk-icon-button>\r\n }\r\n <ng-container\r\n [ngTemplateOutlet]=\"cardTemplate()\"\r\n [ngTemplateOutletContext]=\"cardContext(card, column, i)\"\r\n ></ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n", styles: [".bk-kanban-host{display:block;height:100%}:where(.bk-kanban-board){display:block;height:100%}.bk-kanban{@apply flex items-stretch gap-4 w-full h-full overflow-x-auto;scroll-snap-type:x proximity}.bk-kanban--scroll-wrapper{align-items:flex-start;overflow-y:auto}.bk-kanban--scroll-wrapper .bk-kanban-column-body{overflow-y:visible}:where(.bk-kanban-column){@apply flex flex-col shrink-0 w-[300px] max-w-[85vw] rounded-xl border border-[#EBEDF3] overflow-hidden;background:var(--bk-kanban-column-bg, #f9fafa);scroll-snap-align:start}:where(.bk-kanban-column-header){@apply flex items-center justify-between gap-2 px-3 py-2.5;background:var(--bk-kanban-header-bg, #f9fafa);border-bottom:1px solid #ebedf3}.bk-kanban-column:not(.cdk-drag-disabled)>.bk-kanban-column-header{cursor:grab}.bk-kanban-column.cdk-drag-dragging>.bk-kanban-column-header{cursor:grabbing}.bk-kanban-column-title{@apply text-[13px] font-semibold text-[#15191E] truncate;}.bk-kanban-column-count{@apply shrink-0;}.bk-kanban-column-actions{@apply shrink-0 flex items-center;}.bk-kanban-column-body{@apply flex-1 flex flex-col gap-2 p-2.5 overflow-y-auto;min-height:96px}.bk-kanban-empty{@apply flex flex-1 flex-col items-center justify-center py-6 text-center;}.bk-kanban-empty-text{@apply text-xs font-medium text-[#78829D];}.bk-kanban-add-btn{@apply shrink-0;}.bk-kanban-card{@apply relative bg-white rounded-lg border border-[#EBEDF3] p-3 cursor-pointer;box-shadow:0 1px 2px #1018280a;transition:box-shadow .15s ease,border-color .15s ease}.bk-kanban-remove-btn{@apply absolute top-1.5 right-1.5 z-10;}.bk-kanban-card:hover{@apply border-[#C4CADA];box-shadow:0 2px 6px #10182814}.bk-kanban-card.cdk-drag-dragging{transition:none}.bk-kanban-column-warn{@apply border-amber-300 bg-amber-50;}.bk-kanban-column-warn .bk-kanban-column-header{@apply bg-amber-50 border-amber-200;}.bk-kanban-column-live-over{@apply border-amber-400 bg-amber-50;outline:2px dashed theme(\"colors.amber.400\");outline-offset:-2px}.bk-kanban-column-live-over .bk-kanban-column-header{@apply bg-amber-50 border-amber-200;}.bk-kanban-card.cdk-drag-preview{@apply rounded-lg;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;cursor:grabbing}.bk-kanban-card.cdk-drag-placeholder{@apply border-dashed border-[#C4CADA] bg-[#F1F2F4];box-shadow:none;opacity:.6}.bk-kanban-column-body.cdk-drop-list-dragging .bk-kanban-card:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.bk-kanban-column.cdk-drag-preview{box-shadow:0 8px 10px -6px #0003,0 20px 25px -5px #00000024}.bk-kanban-column.cdk-drag-placeholder{@apply border-dashed border-[#C4CADA] bg-[#F1F2F4];box-shadow:none;opacity:.5}.bk-kanban-column.cdk-drag-placeholder .bk-kanban-column-header,.bk-kanban-column.cdk-drag-placeholder .bk-kanban-column-body{visibility:hidden}.bk-kanban.cdk-drop-list-dragging .bk-kanban-column:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}@media (max-width: 640px){.bk-kanban-column{width:86vw}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i1$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i1$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: BkBadge, selector: "bk-badge", inputs: ["label", "variant", "color", "size", "dot", "removable", "customClass", "customBg", "customBorder", "customText"], outputs: ["clicked"] }, { kind: "component", type: BkButton, selector: "bk-button", inputs: ["variant", "size", "shadow", "label", "leftIcon", "rightIcon", "iconAlt", "type", "loading", "disabled", "buttonClass", "textClass", "spinnerClass"], outputs: ["clicked"] }, { kind: "component", type: BkIconButton, selector: "bk-icon-button", inputs: ["icon", "alt", "variant", "size", "disabled", "buttonClass"], outputs: ["clicked", "hovered"] }], encapsulation: i0.ViewEncapsulation.None });
|
|
14416
14597
|
}
|
|
14417
14598
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: BkKanban, decorators: [{
|
|
14418
14599
|
type: Component,
|
|
14419
|
-
args: [{ selector: 'bk-kanban', standalone: true, exportAs: 'bkKanban', imports: [NgTemplateOutlet, DragDropModule, BkBadge], encapsulation: ViewEncapsulation.None, host: {
|
|
14600
|
+
args: [{ selector: 'bk-kanban', standalone: true, exportAs: 'bkKanban', imports: [NgTemplateOutlet, DragDropModule, BkBadge, BkButton, BkIconButton], encapsulation: ViewEncapsulation.None, host: {
|
|
14420
14601
|
class: 'bk-kanban-host'
|
|
14421
|
-
}, template: "<div class=\"bk-kanban-board\" [class]=\"boardClass()\">\r\n <div\r\n class=\"bk-kanban\"\r\n [class.bk-kanban--scroll-wrapper]=\"columnScrollMode() === 'wrapper'\"\r\n cdkDropList\r\n cdkDropListOrientation=\"horizontal\"\r\n [cdkDropListData]=\"
|
|
14422
|
-
}], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }],
|
|
14602
|
+
}, template: "<div class=\"bk-kanban-board\" [class]=\"boardClass()\">\r\n <div\r\n class=\"bk-kanban\"\r\n [class.bk-kanban--scroll-wrapper]=\"columnScrollMode() === 'wrapper'\"\r\n cdkDropList\r\n cdkDropListOrientation=\"horizontal\"\r\n [cdkDropListData]=\"boardColumns()\"\r\n [cdkDropListDisabled]=\"!dragColumn()\"\r\n (cdkDropListDropped)=\"onColumnDrop($event)\"\r\n >\r\n @for (column of boardColumns(); track column.id) {\r\n <div\r\n class=\"bk-kanban-column\"\r\n cdkDrag\r\n [cdkDragData]=\"column\"\r\n [cdkDragDisabled]=\"!dragColumn()\"\r\n [class]=\"column.columnClass\"\r\n [class.bk-kanban-column-warn]=\"isColumnOverLimit(column)\"\r\n [class.bk-kanban-column-live-over]=\"isLiveOverLimit(column)\"\r\n >\r\n <div class=\"bk-kanban-column-header\" cdkDragHandle [class]=\"column.colHeaderClass\">\r\n <span class=\"bk-kanban-column-title\">{{ column.label }}</span>\r\n <bk-badge\r\n class=\"bk-kanban-column-count\"\r\n [label]=\"columnCountLabel(column)\"\r\n size=\"sm\"\r\n [color]=\"isColumnOverLimit(column) || isLiveOverLimit(column) ? 'Warning' : 'Gray'\"\r\n ></bk-badge>\r\n\r\n @if (columnHeaderActionsTemplate(); as actionsTpl) {\r\n <div class=\"bk-kanban-column-actions\">\r\n <ng-container\r\n [ngTemplateOutlet]=\"actionsTpl\"\r\n [ngTemplateOutletContext]=\"columnActionsContext(column)\"\r\n ></ng-container>\r\n </div>\r\n }\r\n @if (showAddButton(column)) {\r\n <bk-button\r\n class=\"bk-kanban-add-btn\"\r\n variant=\"primary\"\r\n size=\"xxsm\"\r\n [shadow]=\"false\"\r\n label=\"+\"\r\n [buttonClass]=\"'size-6 hover:bg-[#242424]'\"\r\n [attr.aria-label]=\"'Add item to ' + column.label\"\r\n (clicked)=\"onAddButtonClick(column)\"\r\n ></bk-button>\r\n }\r\n </div>\r\n\r\n <div\r\n class=\"bk-kanban-column-body\"\r\n cdkDropList\r\n [id]=\"column.id\"\r\n [cdkDropListData]=\"column.items\"\r\n [cdkDropListConnectedTo]=\"connectedListIds(column.id)\"\r\n [cdkDropListSortingDisabled]=\"sortingDisabled()\"\r\n [cdkDropListDisabled]=\"!dragActive()\"\r\n (cdkDropListDropped)=\"onItemDrop($event, column)\"\r\n (cdkDropListEntered)=\"onListEntered($event, column)\"\r\n (cdkDropListExited)=\"onListExited(column)\"\r\n >\r\n @if (column.items.length === 0) {\r\n <div class=\"bk-kanban-empty\">\r\n @if (emptyStateTemplate(); as emptyTpl) {\r\n <ng-container\r\n [ngTemplateOutlet]=\"emptyTpl\"\r\n [ngTemplateOutletContext]=\"emptyStateContext(column)\"\r\n ></ng-container>\r\n } @else {\r\n <span class=\"bk-kanban-empty-text\">{{ emptyMessageFor(column) }}</span>\r\n }\r\n </div>\r\n }\r\n\r\n @for (card of column.items; track resolveTrackBy(card, $index); let i = $index) {\r\n <div\r\n class=\"bk-kanban-card\"\r\n cdkDrag\r\n [cdkDragData]=\"card\"\r\n [cdkDragDisabled]=\"!dragActive()\"\r\n (cdkDragStarted)=\"onDragStarted(column)\"\r\n (cdkDragEnded)=\"onDragEnded()\"\r\n (click)=\"onItemClick($event, card)\"\r\n >\r\n @if (showRemoveButton(column)) {\r\n <bk-icon-button\r\n class=\"bk-kanban-remove-btn\"\r\n [icon]=\"removeButtonIcon(card)\"\r\n [alt]=\"'Remove ' + card.label\"\r\n [variant]=\"removeButtonVariant(card)\"\r\n size=\"xxxsm\"\r\n (hovered)=\"onRemoveButtonHover(card, $event)\"\r\n (mousedown)=\"$event.stopPropagation()\"\r\n (pointerdown)=\"$event.stopPropagation()\"\r\n (click)=\"$event.stopPropagation()\"\r\n (clicked)=\"onRemoveButtonClick(column, card)\"\r\n ></bk-icon-button>\r\n }\r\n <ng-container\r\n [ngTemplateOutlet]=\"cardTemplate()\"\r\n [ngTemplateOutletContext]=\"cardContext(card, column, i)\"\r\n ></ng-container>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n", styles: [".bk-kanban-host{display:block;height:100%}:where(.bk-kanban-board){display:block;height:100%}.bk-kanban{@apply flex items-stretch gap-4 w-full h-full overflow-x-auto;scroll-snap-type:x proximity}.bk-kanban--scroll-wrapper{align-items:flex-start;overflow-y:auto}.bk-kanban--scroll-wrapper .bk-kanban-column-body{overflow-y:visible}:where(.bk-kanban-column){@apply flex flex-col shrink-0 w-[300px] max-w-[85vw] rounded-xl border border-[#EBEDF3] overflow-hidden;background:var(--bk-kanban-column-bg, #f9fafa);scroll-snap-align:start}:where(.bk-kanban-column-header){@apply flex items-center justify-between gap-2 px-3 py-2.5;background:var(--bk-kanban-header-bg, #f9fafa);border-bottom:1px solid #ebedf3}.bk-kanban-column:not(.cdk-drag-disabled)>.bk-kanban-column-header{cursor:grab}.bk-kanban-column.cdk-drag-dragging>.bk-kanban-column-header{cursor:grabbing}.bk-kanban-column-title{@apply text-[13px] font-semibold text-[#15191E] truncate;}.bk-kanban-column-count{@apply shrink-0;}.bk-kanban-column-actions{@apply shrink-0 flex items-center;}.bk-kanban-column-body{@apply flex-1 flex flex-col gap-2 p-2.5 overflow-y-auto;min-height:96px}.bk-kanban-empty{@apply flex flex-1 flex-col items-center justify-center py-6 text-center;}.bk-kanban-empty-text{@apply text-xs font-medium text-[#78829D];}.bk-kanban-add-btn{@apply shrink-0;}.bk-kanban-card{@apply relative bg-white rounded-lg border border-[#EBEDF3] p-3 cursor-pointer;box-shadow:0 1px 2px #1018280a;transition:box-shadow .15s ease,border-color .15s ease}.bk-kanban-remove-btn{@apply absolute top-1.5 right-1.5 z-10;}.bk-kanban-card:hover{@apply border-[#C4CADA];box-shadow:0 2px 6px #10182814}.bk-kanban-card.cdk-drag-dragging{transition:none}.bk-kanban-column-warn{@apply border-amber-300 bg-amber-50;}.bk-kanban-column-warn .bk-kanban-column-header{@apply bg-amber-50 border-amber-200;}.bk-kanban-column-live-over{@apply border-amber-400 bg-amber-50;outline:2px dashed theme(\"colors.amber.400\");outline-offset:-2px}.bk-kanban-column-live-over .bk-kanban-column-header{@apply bg-amber-50 border-amber-200;}.bk-kanban-card.cdk-drag-preview{@apply rounded-lg;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;cursor:grabbing}.bk-kanban-card.cdk-drag-placeholder{@apply border-dashed border-[#C4CADA] bg-[#F1F2F4];box-shadow:none;opacity:.6}.bk-kanban-column-body.cdk-drop-list-dragging .bk-kanban-card:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}.bk-kanban-column.cdk-drag-preview{box-shadow:0 8px 10px -6px #0003,0 20px 25px -5px #00000024}.bk-kanban-column.cdk-drag-placeholder{@apply border-dashed border-[#C4CADA] bg-[#F1F2F4];box-shadow:none;opacity:.5}.bk-kanban-column.cdk-drag-placeholder .bk-kanban-column-header,.bk-kanban-column.cdk-drag-placeholder .bk-kanban-column-body{visibility:hidden}.bk-kanban.cdk-drop-list-dragging .bk-kanban-column:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}@media (max-width: 640px){.bk-kanban-column{width:86vw}}\n"] }]
|
|
14603
|
+
}], propDecorators: { columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: true }] }], cardTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardTemplate", required: true }] }], movementRestriction: [{ type: i0.Input, args: [{ isSignal: true, alias: "movementRestriction", required: false }] }], wipLimitBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "wipLimitBehavior", required: false }] }], dragColumn: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragColumn", required: false }] }], columnReorderPayload: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnReorderPayload", required: false }] }], validateMove: [{ type: i0.Input, args: [{ isSignal: true, alias: "validateMove", required: false }] }], columnScrollMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnScrollMode", required: false }] }], boardClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "boardClass", required: false }] }], trackBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "trackBy", required: false }] }], columnHeaderActionsTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnHeaderActionsTemplate", required: false }] }], emptyStateTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyStateTemplate", required: false }] }], emptyMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyMessage", required: false }] }], dragPreviewMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragPreviewMode", required: false }] }], showAddItem: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAddItem", required: false }] }], showRemoveItem: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRemoveItem", required: false }] }], columnsChanged: [{ type: i0.Output, args: ["columnsChanged"] }], columnLimitExceeded: [{ type: i0.Output, args: ["columnLimitExceeded"] }], itemClicked: [{ type: i0.Output, args: ["itemClicked"] }], itemMoveBetweenColumns: [{ type: i0.Output, args: ["itemMoveBetweenColumns"] }], columnActionTriggered: [{ type: i0.Output, args: ["columnActionTriggered"] }] } });
|
|
14423
14604
|
|
|
14424
14605
|
/**
|
|
14425
14606
|
* Marks an element inside a `cardTemplate` as its own click region.
|
|
@@ -14461,10 +14642,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
14461
14642
|
/**
|
|
14462
14643
|
* Types for bk-kanban.
|
|
14463
14644
|
*
|
|
14464
|
-
*
|
|
14465
|
-
*
|
|
14466
|
-
*
|
|
14467
|
-
*
|
|
14645
|
+
* Columns own their items — a single nested tree is the source of truth:
|
|
14646
|
+
* `columns: KanbanColumn<T>[]`, each carrying its own `items: T[]`. There is
|
|
14647
|
+
* no separate flat cards array anywhere; grouping, ordering, and lookup all
|
|
14648
|
+
* fall out of the tree itself.
|
|
14649
|
+
*
|
|
14650
|
+
* Every drag, add, and remove operation emits only the column(s) actually
|
|
14651
|
+
* affected — never the whole board — each with its complete, current
|
|
14652
|
+
* `items` (column *reordering* is the one exception — see
|
|
14653
|
+
* `BkKanbanColumnReorderPayload`). There's deliberately no
|
|
14654
|
+
* `itemsMovedWithin` / `itemsMovedBetween` pair of "what changed" fields:
|
|
14655
|
+
* the consumer already gets the complete, ordered `items` for every affected
|
|
14656
|
+
* column, so diffing that against whatever it had before tells it exactly
|
|
14657
|
+
* what moved and where without the board also maintaining a second,
|
|
14658
|
+
* redundant description of the same fact.
|
|
14468
14659
|
*/
|
|
14469
14660
|
|
|
14470
14661
|
/*
|