@mosaicoo/kanban 0.1.0
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/LICENSE +201 -0
- package/README.md +44 -0
- package/fesm2022/mosaicoo-kanban-adapters.mjs +417 -0
- package/fesm2022/mosaicoo-kanban-core.mjs +1483 -0
- package/fesm2022/mosaicoo-kanban-ui.mjs +2299 -0
- package/fesm2022/mosaicoo-kanban.mjs +13 -0
- package/package.json +60 -0
- package/styles/kanban-tokens.css +66 -0
- package/types/mosaicoo-kanban-adapters.d.ts +166 -0
- package/types/mosaicoo-kanban-core.d.ts +689 -0
- package/types/mosaicoo-kanban-ui.d.ts +538 -0
- package/types/mosaicoo-kanban.d.ts +2 -0
|
@@ -0,0 +1,2299 @@
|
|
|
1
|
+
import { CdkDropList, CdkDrag, CdkDragHandle, CdkDragPreview } from '@angular/cdk/drag-drop';
|
|
2
|
+
import { CdkScrollable } from '@angular/cdk/scrolling';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { inject, TemplateRef, Directive, input, ChangeDetectionStrategy, Component, Injectable, output, computed, contentChild, signal, ElementRef, effect } from '@angular/core';
|
|
5
|
+
import { entriesOf, MKB_VERSION } from '@mosaicoo/kanban/core';
|
|
6
|
+
import { NgClass, NgTemplateOutlet } from '@angular/common';
|
|
7
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Template de cartão custom, projetado no board:
|
|
11
|
+
*
|
|
12
|
+
* <mos-kanban-board [board]="board">
|
|
13
|
+
* <ng-template mosKanbanCard let-item>{{ item['title'] }}</ng-template>
|
|
14
|
+
* </mos-kanban-board>
|
|
15
|
+
*
|
|
16
|
+
* Sem o template, o board renderiza o cartão default.
|
|
17
|
+
*/
|
|
18
|
+
class MosKanbanCardDef {
|
|
19
|
+
template = inject((TemplateRef));
|
|
20
|
+
static ngTemplateContextGuard(_dir, ctx) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MosKanbanCardDef, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
24
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.18", type: MosKanbanCardDef, isStandalone: true, selector: "ng-template[mosKanbanCard]", ngImport: i0 });
|
|
25
|
+
}
|
|
26
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MosKanbanCardDef, decorators: [{
|
|
27
|
+
type: Directive,
|
|
28
|
+
args: [{ selector: 'ng-template[mosKanbanCard]' }]
|
|
29
|
+
}] });
|
|
30
|
+
|
|
31
|
+
/** Cartão DEFAULT (usado quando o consumidor não projeta um template
|
|
32
|
+
* mosKanbanCard): título + id discreto. Visual 100% por tokens. */
|
|
33
|
+
class MosKanbanCard {
|
|
34
|
+
item = input.required(...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
|
|
35
|
+
title() {
|
|
36
|
+
const title = this.item()['title'];
|
|
37
|
+
return typeof title === 'string' && title !== '' ? title : this.item().id;
|
|
38
|
+
}
|
|
39
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MosKanbanCard, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
40
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.18", type: MosKanbanCard, isStandalone: true, selector: "mos-kanban-card", inputs: { item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
|
|
41
|
+
<span class="mkb-card-title">{{ title() }}</span>
|
|
42
|
+
`, isInline: true, styles: [":host{display:block}.mkb-card-title{font-size:13px;font-weight:500;color:var(--mkb-foreground, var(--color-foreground, #241f33));overflow-wrap:anywhere}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
43
|
+
}
|
|
44
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MosKanbanCard, decorators: [{
|
|
45
|
+
type: Component,
|
|
46
|
+
args: [{ selector: 'mos-kanban-card', changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
47
|
+
<span class="mkb-card-title">{{ title() }}</span>
|
|
48
|
+
`, styles: [":host{display:block}.mkb-card-title{font-size:13px;font-weight:500;color:var(--mkb-foreground, var(--color-foreground, #241f33));overflow-wrap:anywhere}\n"] }]
|
|
49
|
+
}], propDecorators: { item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }] } });
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Cancelamento por Esc (spike D0/E4): o CDK não cancela drag nativamente.
|
|
53
|
+
* Workaround: Esc arma este flag e dispara um mouseup sintético — o drop
|
|
54
|
+
* acontece, mas os handlers CONSOMEM o flag e não executam comando algum;
|
|
55
|
+
* como a UI renderiza do estado (inalterado), o elemento volta à origem.
|
|
56
|
+
* Instância por board (provider do componente), nunca singleton de módulo.
|
|
57
|
+
*/
|
|
58
|
+
class DragCancelFlag {
|
|
59
|
+
canceled = false;
|
|
60
|
+
arm() {
|
|
61
|
+
this.canceled = true;
|
|
62
|
+
}
|
|
63
|
+
/** Lê E limpa (check-and-clear em todo handler de drop). */
|
|
64
|
+
consume() {
|
|
65
|
+
const was = this.canceled;
|
|
66
|
+
this.canceled = false;
|
|
67
|
+
return was;
|
|
68
|
+
}
|
|
69
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: DragCancelFlag, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
70
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: DragCancelFlag });
|
|
71
|
+
}
|
|
72
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: DragCancelFlag, decorators: [{
|
|
73
|
+
type: Injectable
|
|
74
|
+
}] });
|
|
75
|
+
/**
|
|
76
|
+
* Costura entre o GESTO de arrasto e os COMANDOS do core — deliberadamente
|
|
77
|
+
* agnóstica de CDK (docs/01 §6): os componentes traduzem eventos do CDK para
|
|
78
|
+
* estas funções; trocar a engine de drag no futuro não toca em regra alguma.
|
|
79
|
+
*
|
|
80
|
+
* Preview e commit usam o MESMO caminho (board.canMove / moveItem): o que o
|
|
81
|
+
* indicador de drop mostra é exatamente o que o solto faz.
|
|
82
|
+
*
|
|
83
|
+
* Uma "célula" é a interseção coluna × lane; sem eixo de lanes, a célula é a
|
|
84
|
+
* coluna inteira (laneId undefined).
|
|
85
|
+
*/
|
|
86
|
+
/**
|
|
87
|
+
* Id DOM estável da drop list de uma célula (spike D0/E1): as células são
|
|
88
|
+
* conectadas entre si por `cdkDropListConnectedTo` EXPLÍCITO — nada de
|
|
89
|
+
* `cdkDropListGroup`, que auto-conectaria também as listas de reorder de
|
|
90
|
+
* colunas/lanes (ancestrais) e corromperia a detecção de alvo do CDK.
|
|
91
|
+
* encodeURIComponent: ids de grupo podem ter espaço (inválido em id de DOM).
|
|
92
|
+
*/
|
|
93
|
+
function cellListId(columnId, laneId) {
|
|
94
|
+
const col = encodeURIComponent(columnId);
|
|
95
|
+
return laneId === undefined
|
|
96
|
+
? `mkb-cell-${col}`
|
|
97
|
+
: `mkb-cell-${col}--${encodeURIComponent(laneId)}`;
|
|
98
|
+
}
|
|
99
|
+
/** Id DOM estável da drop list de ENTRIES de um card (bloco items): listas
|
|
100
|
+
* do MESMO field conectam-se entre cards quando 'move-across' está ligado —
|
|
101
|
+
* mesma topologia explícita das células. */
|
|
102
|
+
function entriesListId(cardId, field) {
|
|
103
|
+
return `mkb-entries-${encodeURIComponent(cardId)}--${encodeURIComponent(field)}`;
|
|
104
|
+
}
|
|
105
|
+
/** O item pode ENTRAR na célula? (indicador durante o arrasto) */
|
|
106
|
+
function canEnterCell(board, itemId, columnId, laneId) {
|
|
107
|
+
return board.canMove(itemId, { column: columnId, lane: laneId }).ok;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Converte o índice VISUAL do drop (posição na lista visível, sem o item
|
|
111
|
+
* arrastado) para o índice da célula no core (ordem manual entre TODOS os
|
|
112
|
+
* itens da célula, sem o movido) — os dois divergem quando há filtro ativo.
|
|
113
|
+
*/
|
|
114
|
+
function resolveDropIndex(board, itemId, columnId, visibleIndex, laneId) {
|
|
115
|
+
const visible = board
|
|
116
|
+
.visibleItemsIn(columnId, laneId)
|
|
117
|
+
.filter((item) => item.id !== itemId);
|
|
118
|
+
if (visibleIndex >= visible.length)
|
|
119
|
+
return undefined; // fim da célula
|
|
120
|
+
const referenceId = visible[visibleIndex]?.id;
|
|
121
|
+
const cellOrder = board
|
|
122
|
+
.itemsIn(columnId, laneId)
|
|
123
|
+
.filter((item) => item.id !== itemId);
|
|
124
|
+
const index = cellOrder.findIndex((item) => item.id === referenceId);
|
|
125
|
+
return index === -1 ? undefined : index;
|
|
126
|
+
}
|
|
127
|
+
/** Executa o drop como comando do core. */
|
|
128
|
+
function executeDrop(board, itemId, columnId, visibleIndex, laneId) {
|
|
129
|
+
return board.execute({
|
|
130
|
+
type: 'moveItem',
|
|
131
|
+
itemId,
|
|
132
|
+
to: {
|
|
133
|
+
column: columnId,
|
|
134
|
+
lane: laneId,
|
|
135
|
+
index: resolveDropIndex(board, itemId, columnId, visibleIndex, laneId),
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const isEmpty = (v) => v === undefined || v === null || v === '';
|
|
141
|
+
const toDate = (value) => {
|
|
142
|
+
if (value instanceof Date)
|
|
143
|
+
return isNaN(value.getTime()) ? null : value;
|
|
144
|
+
if (typeof value === 'number' || typeof value === 'string') {
|
|
145
|
+
const d = new Date(value);
|
|
146
|
+
return isNaN(d.getTime()) ? null : d;
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
};
|
|
150
|
+
/** Texto simples com prefixo/sufixo; `format` (registry) tem precedência. */
|
|
151
|
+
function formatText(value, opts, item, ctx) {
|
|
152
|
+
if (opts.format) {
|
|
153
|
+
const fn = ctx.registry?.get(opts.format);
|
|
154
|
+
if (fn)
|
|
155
|
+
return fn(value, item);
|
|
156
|
+
}
|
|
157
|
+
if (isEmpty(value))
|
|
158
|
+
return '';
|
|
159
|
+
return `${opts.prefix ?? ''}${String(value)}${opts.suffix ?? ''}`;
|
|
160
|
+
}
|
|
161
|
+
function formatNumber(value, opts, ctx) {
|
|
162
|
+
if (isEmpty(value))
|
|
163
|
+
return '';
|
|
164
|
+
const num = typeof value === 'number' ? value : Number(value);
|
|
165
|
+
if (isNaN(num))
|
|
166
|
+
return String(value);
|
|
167
|
+
const formatted = new Intl.NumberFormat(ctx.locale, {
|
|
168
|
+
style: opts.style ?? 'decimal',
|
|
169
|
+
currency: opts.currency,
|
|
170
|
+
minimumFractionDigits: opts.decimals,
|
|
171
|
+
maximumFractionDigits: opts.decimals,
|
|
172
|
+
}).format(num);
|
|
173
|
+
return `${opts.prefix ?? ''}${formatted}${opts.suffix ?? ''}`;
|
|
174
|
+
}
|
|
175
|
+
function formatDate(value, opts, ctx) {
|
|
176
|
+
if (isEmpty(value))
|
|
177
|
+
return '';
|
|
178
|
+
const date = toDate(value);
|
|
179
|
+
if (!date)
|
|
180
|
+
return String(value);
|
|
181
|
+
if (opts.relative)
|
|
182
|
+
return formatRelative(date, ctx.locale);
|
|
183
|
+
return new Intl.DateTimeFormat(ctx.locale, {
|
|
184
|
+
dateStyle: opts.withDate === false ? undefined : (opts.dateStyle ?? 'medium'),
|
|
185
|
+
timeStyle: opts.withTime ? (opts.timeStyle ?? 'short') : undefined,
|
|
186
|
+
}).format(date);
|
|
187
|
+
}
|
|
188
|
+
const RELATIVE_UNITS = [
|
|
189
|
+
['year', 31536000000],
|
|
190
|
+
['month', 2592000000],
|
|
191
|
+
['week', 604800000],
|
|
192
|
+
['day', 86400000],
|
|
193
|
+
['hour', 3600000],
|
|
194
|
+
['minute', 60000],
|
|
195
|
+
['second', 1000],
|
|
196
|
+
];
|
|
197
|
+
/** "há 4 horas" / "em 2 dias" via Intl.RelativeTimeFormat, maior unidade. */
|
|
198
|
+
function formatRelative(date, locale) {
|
|
199
|
+
const diff = date.getTime() - Date.now();
|
|
200
|
+
const abs = Math.abs(diff);
|
|
201
|
+
const rtf = new Intl.RelativeTimeFormat(locale, { numeric: 'auto' });
|
|
202
|
+
for (const [unit, ms] of RELATIVE_UNITS) {
|
|
203
|
+
if (abs >= ms || unit === 'second') {
|
|
204
|
+
return rtf.format(Math.round(diff / ms), unit);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return rtf.format(0, 'second');
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Resolve um ícone pelo nome no registry (`icon:<nome>`, valor registrado pelo
|
|
211
|
+
* host). Retorna { markup } quando o valor é SVG/HTML (host-confiável → seguro
|
|
212
|
+
* dar bypass) ou { text } para emoji/rótulo curto. Nome não registrado cai no
|
|
213
|
+
* próprio nome como texto (fallback visível, não quebra o layout).
|
|
214
|
+
*/
|
|
215
|
+
function resolveIcon(name, registry) {
|
|
216
|
+
if (!name)
|
|
217
|
+
return {};
|
|
218
|
+
const value = registry?.get(`icon:${name}`);
|
|
219
|
+
if (typeof value === 'string') {
|
|
220
|
+
return value.trimStart().startsWith('<') ? { markup: value } : { text: value };
|
|
221
|
+
}
|
|
222
|
+
return { text: name };
|
|
223
|
+
}
|
|
224
|
+
/** Iniciais para o avatar sem foto (ex.: "Ana Paula" → "AP"). */
|
|
225
|
+
function initials(name) {
|
|
226
|
+
if (typeof name !== 'string' || name.trim() === '')
|
|
227
|
+
return '?';
|
|
228
|
+
const parts = name.trim().split(/\s+/);
|
|
229
|
+
const first = parts[0]?.[0] ?? '';
|
|
230
|
+
const last = parts.length > 1 ? (parts[parts.length - 1]?.[0] ?? '') : '';
|
|
231
|
+
return (first + last).toUpperCase();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Renderiza a árvore de blocos DECLARATIVOS de um cartão (docs/01 §4; F5.5).
|
|
236
|
+
* Recursivo (o bloco `row` reentra no próprio componente). Estilo é CSS
|
|
237
|
+
* externo: cada bloco recebe classes-base estáveis (`mkb-block-*`,
|
|
238
|
+
* `mkb-variant-*`, `mkb-tone-*`) + as classes do host (atributo `class`).
|
|
239
|
+
*/
|
|
240
|
+
class MosKanbanCardBlocks {
|
|
241
|
+
blocks = input.required(...(ngDevMode ? [{ debugName: "blocks" }] : /* istanbul ignore next */ []));
|
|
242
|
+
item = input.required(...(ngDevMode ? [{ debugName: "item" }] : /* istanbul ignore next */ []));
|
|
243
|
+
registry = input(undefined, ...(ngDevMode ? [{ debugName: "registry" }] : /* istanbul ignore next */ []));
|
|
244
|
+
locale = input(undefined, ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
245
|
+
column = input(undefined, ...(ngDevMode ? [{ debugName: "column" }] : /* istanbul ignore next */ []));
|
|
246
|
+
lane = input(undefined, ...(ngDevMode ? [{ debugName: "lane" }] : /* istanbul ignore next */ []));
|
|
247
|
+
/** Ids de TODOS os cards do board — conexões do 'move-across'. */
|
|
248
|
+
allCardIds = input([], ...(ngDevMode ? [{ debugName: "allCardIds" }] : /* istanbul ignore next */ []));
|
|
249
|
+
/** Preview de drop de entry entre cards (vem do board.canMoveEntry). */
|
|
250
|
+
canDropEntry = input(null, ...(ngDevMode ? [{ debugName: "canDropEntry" }] : /* istanbul ignore next */ []));
|
|
251
|
+
action = output();
|
|
252
|
+
entryCheck = output();
|
|
253
|
+
entryAction = output();
|
|
254
|
+
entryMove = output();
|
|
255
|
+
/** Ciclo do gesto de arraste de entry (docs/02 §9). */
|
|
256
|
+
lifecycle = output();
|
|
257
|
+
sanitizer = inject(DomSanitizer);
|
|
258
|
+
ctx = computed(() => ({
|
|
259
|
+
locale: this.locale(),
|
|
260
|
+
registry: this.registry(),
|
|
261
|
+
}), ...(ngDevMode ? [{ debugName: "ctx" }] : /* istanbul ignore next */ []));
|
|
262
|
+
String = String;
|
|
263
|
+
raw(field) {
|
|
264
|
+
return field === undefined ? undefined : this.item()[field];
|
|
265
|
+
}
|
|
266
|
+
text(block) {
|
|
267
|
+
return formatText(this.raw(block.field), block, this.item(), this.ctx());
|
|
268
|
+
}
|
|
269
|
+
num(block) {
|
|
270
|
+
return formatNumber(this.raw(block.field), block, this.ctx());
|
|
271
|
+
}
|
|
272
|
+
dateVal(block) {
|
|
273
|
+
const value = this.raw(block.field);
|
|
274
|
+
if (block.type === 'time') {
|
|
275
|
+
return formatDate(value, { withDate: false, withTime: true, timeStyle: block.timeStyle }, this.ctx());
|
|
276
|
+
}
|
|
277
|
+
if (block.type === 'datetime') {
|
|
278
|
+
return formatDate(value, { withTime: true, dateStyle: block.dateStyle, timeStyle: block.timeStyle, relative: block.relative }, this.ctx());
|
|
279
|
+
}
|
|
280
|
+
return formatDate(value, { dateStyle: block.dateStyle, relative: block.relative }, this.ctx());
|
|
281
|
+
}
|
|
282
|
+
valueStyle(block) {
|
|
283
|
+
const value = this.raw(block.field);
|
|
284
|
+
return block.map?.[String(value)] ?? {};
|
|
285
|
+
}
|
|
286
|
+
factText(fact) {
|
|
287
|
+
return `${fact.prefix ?? ''}${String(this.raw(fact.field))}${fact.suffix ?? ''}`;
|
|
288
|
+
}
|
|
289
|
+
avatarName(block) {
|
|
290
|
+
const name = this.raw(block.nameField);
|
|
291
|
+
if (typeof name === 'string' && name !== '')
|
|
292
|
+
return name;
|
|
293
|
+
return this.raw(block.photoField) ? ' ' : '';
|
|
294
|
+
}
|
|
295
|
+
ini(name) {
|
|
296
|
+
return initials(name);
|
|
297
|
+
}
|
|
298
|
+
progressValue(block) {
|
|
299
|
+
const v = Number(this.raw(block.field));
|
|
300
|
+
return isNaN(v) ? 0 : v;
|
|
301
|
+
}
|
|
302
|
+
progressPct(block) {
|
|
303
|
+
const max = block.max ?? 100;
|
|
304
|
+
return max <= 0 ? 0 : Math.max(0, Math.min(100, (this.progressValue(block) / max) * 100));
|
|
305
|
+
}
|
|
306
|
+
visibleActions(block) {
|
|
307
|
+
return block.items.filter((a) => this.matchesWhen(a));
|
|
308
|
+
}
|
|
309
|
+
// ---- bloco 'items' (entries do card) --------------------------------------
|
|
310
|
+
entryList(block) {
|
|
311
|
+
return entriesOf(this.item(), block.field);
|
|
312
|
+
}
|
|
313
|
+
visibleEntries(block) {
|
|
314
|
+
const list = this.entryList(block);
|
|
315
|
+
return block.maxVisible !== undefined ? list.slice(0, block.maxVisible) : list;
|
|
316
|
+
}
|
|
317
|
+
hiddenCount(block) {
|
|
318
|
+
return this.entryList(block).length - this.visibleEntries(block).length;
|
|
319
|
+
}
|
|
320
|
+
hasCap(block, cap) {
|
|
321
|
+
return block.capabilities?.includes(cap) ?? false;
|
|
322
|
+
}
|
|
323
|
+
/** 'move-across' implica 'reorder'. */
|
|
324
|
+
reorderEnabled(block) {
|
|
325
|
+
return this.hasCap(block, 'reorder') || this.hasCap(block, 'move-across');
|
|
326
|
+
}
|
|
327
|
+
entriesId(block) {
|
|
328
|
+
return entriesListId(this.item().id, block.field);
|
|
329
|
+
}
|
|
330
|
+
/** Conexões do move-across: listas do MESMO field nos outros cards. */
|
|
331
|
+
entryConnections(block) {
|
|
332
|
+
if (!this.hasCap(block, 'move-across'))
|
|
333
|
+
return [];
|
|
334
|
+
const self = this.item().id;
|
|
335
|
+
return this.allCardIds()
|
|
336
|
+
.filter((id) => id !== self)
|
|
337
|
+
.map((id) => entriesListId(id, block.field));
|
|
338
|
+
}
|
|
339
|
+
/** enterPredicate por bloco (identidade cacheada p/ o CDK). */
|
|
340
|
+
predicateCache = new WeakMap();
|
|
341
|
+
entryPredicate(block) {
|
|
342
|
+
let fn = this.predicateCache.get(block);
|
|
343
|
+
if (!fn) {
|
|
344
|
+
fn = (drag) => {
|
|
345
|
+
const fromCardId = drag.dropContainer.data;
|
|
346
|
+
const toCardId = this.item().id;
|
|
347
|
+
if (fromCardId === toCardId)
|
|
348
|
+
return true; // reorder local
|
|
349
|
+
const can = this.canDropEntry();
|
|
350
|
+
return can
|
|
351
|
+
? can({ fromCardId, toCardId, field: block.field, entryId: drag.data })
|
|
352
|
+
: true;
|
|
353
|
+
};
|
|
354
|
+
this.predicateCache.set(block, fn);
|
|
355
|
+
}
|
|
356
|
+
return fn;
|
|
357
|
+
}
|
|
358
|
+
/** Ctrl+↑/↓ move a entry focada (índice entre as remanescentes). */
|
|
359
|
+
onEntryKeydown(block, entry, event) {
|
|
360
|
+
if (!this.reorderEnabled(block) || !(event.ctrlKey || event.metaKey))
|
|
361
|
+
return;
|
|
362
|
+
if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown')
|
|
363
|
+
return;
|
|
364
|
+
event.preventDefault();
|
|
365
|
+
event.stopPropagation(); // não aciona o teclado do CARD
|
|
366
|
+
const list = this.entryList(block);
|
|
367
|
+
const idx = list.findIndex((e) => e.id === entry.id);
|
|
368
|
+
const target = idx + (event.key === 'ArrowDown' ? 1 : -1);
|
|
369
|
+
if (target < 0 || target >= list.length)
|
|
370
|
+
return;
|
|
371
|
+
this.entryMove.emit({
|
|
372
|
+
cardId: this.item().id,
|
|
373
|
+
field: block.field,
|
|
374
|
+
entryId: entry.id,
|
|
375
|
+
index: target,
|
|
376
|
+
});
|
|
377
|
+
// refoco após o re-render
|
|
378
|
+
const host = event.target.closest('.mkb-block-items');
|
|
379
|
+
setTimeout(() => {
|
|
380
|
+
const rows = host?.querySelectorAll('[data-entry]') ?? [];
|
|
381
|
+
for (const row of Array.from(rows)) {
|
|
382
|
+
if (row.dataset['entry'] === entry.id) {
|
|
383
|
+
row.focus();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
isDone(block, entry) {
|
|
390
|
+
return block.checkField !== undefined && entry[block.checkField] === true;
|
|
391
|
+
}
|
|
392
|
+
entryLabel(block, entry) {
|
|
393
|
+
const label = entry[block.labelField ?? 'label'];
|
|
394
|
+
return typeof label === 'string' && label !== '' ? label : entry.id;
|
|
395
|
+
}
|
|
396
|
+
doneCount(block) {
|
|
397
|
+
return this.entryList(block).filter((e) => this.isDone(block, e)).length;
|
|
398
|
+
}
|
|
399
|
+
toggleEntry(block, entry) {
|
|
400
|
+
if (!block.checkField)
|
|
401
|
+
return;
|
|
402
|
+
this.entryCheck.emit({
|
|
403
|
+
cardId: this.item().id,
|
|
404
|
+
field: block.field,
|
|
405
|
+
entryId: entry.id,
|
|
406
|
+
checkField: block.checkField,
|
|
407
|
+
value: !this.isDone(block, entry),
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
emitEntryAction(_block, entry, actionId, event) {
|
|
411
|
+
event.stopPropagation(); // não seleciona o card
|
|
412
|
+
this.entryAction.emit({ card: this.item(), entry, actionId });
|
|
413
|
+
}
|
|
414
|
+
/** CDK: currentIndex == posição entre as REMANESCENTES (mesma semântica do
|
|
415
|
+
* índice do comando moveEntry); cross-container → move entre cards. */
|
|
416
|
+
onEntryDrop(block, event) {
|
|
417
|
+
const fromCardId = event.previousContainer.data;
|
|
418
|
+
const toCardId = this.item().id;
|
|
419
|
+
const cross = fromCardId !== toCardId;
|
|
420
|
+
if (!cross && event.previousIndex === event.currentIndex)
|
|
421
|
+
return;
|
|
422
|
+
this.entryMove.emit({
|
|
423
|
+
cardId: fromCardId,
|
|
424
|
+
field: block.field,
|
|
425
|
+
entryId: event.item.data,
|
|
426
|
+
index: event.currentIndex,
|
|
427
|
+
toCardId: cross ? toCardId : undefined,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
matchesWhen(action) {
|
|
431
|
+
const when = action.when;
|
|
432
|
+
if (!when)
|
|
433
|
+
return true;
|
|
434
|
+
const has = (side, value) => side === undefined ||
|
|
435
|
+
(Array.isArray(side) ? side.includes(value) : side === value);
|
|
436
|
+
return has(when.column, this.column()) && has(when.lane, this.lane());
|
|
437
|
+
}
|
|
438
|
+
emitAction(action, event) {
|
|
439
|
+
event.stopPropagation(); // não seleciona o cartão ao clicar na ação
|
|
440
|
+
this.action.emit({ item: this.item(), actionId: action.id });
|
|
441
|
+
}
|
|
442
|
+
icon(name) {
|
|
443
|
+
const resolved = resolveIcon(name, this.registry());
|
|
444
|
+
return resolved.markup
|
|
445
|
+
? this.sanitizer.bypassSecurityTrustHtml(resolved.markup)
|
|
446
|
+
: (resolved.text ?? '');
|
|
447
|
+
}
|
|
448
|
+
customHtml(renderer) {
|
|
449
|
+
const fn = this.registry()?.get(`renderer:${renderer}`);
|
|
450
|
+
return fn ? this.sanitizer.bypassSecurityTrustHtml(fn(this.item())) : '';
|
|
451
|
+
}
|
|
452
|
+
classes(block, tone) {
|
|
453
|
+
const out = ['mkb-block', `mkb-block-${block.type}`];
|
|
454
|
+
if ('variant' in block && block.variant)
|
|
455
|
+
out.push(`mkb-variant-${block.variant}`);
|
|
456
|
+
if (tone)
|
|
457
|
+
out.push(`mkb-tone-${tone}`);
|
|
458
|
+
if (block.class) {
|
|
459
|
+
Array.isArray(block.class) ? out.push(...block.class) : out.push(block.class);
|
|
460
|
+
}
|
|
461
|
+
return out;
|
|
462
|
+
}
|
|
463
|
+
rowClasses(block) {
|
|
464
|
+
const out = this.classes(block);
|
|
465
|
+
if (block.align && block.align !== 'start')
|
|
466
|
+
out.push(`mkb-row-${block.align}`);
|
|
467
|
+
if (block.wrap)
|
|
468
|
+
out.push('mkb-row-wrap');
|
|
469
|
+
return out;
|
|
470
|
+
}
|
|
471
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MosKanbanCardBlocks, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
472
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: MosKanbanCardBlocks, isStandalone: true, selector: "mos-kanban-card-blocks", inputs: { blocks: { classPropertyName: "blocks", publicName: "blocks", isSignal: true, isRequired: true, transformFunction: null }, item: { classPropertyName: "item", publicName: "item", isSignal: true, isRequired: true, transformFunction: null }, registry: { classPropertyName: "registry", publicName: "registry", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: false, transformFunction: null }, lane: { classPropertyName: "lane", publicName: "lane", isSignal: true, isRequired: false, transformFunction: null }, allCardIds: { classPropertyName: "allCardIds", publicName: "allCardIds", isSignal: true, isRequired: false, transformFunction: null }, canDropEntry: { classPropertyName: "canDropEntry", publicName: "canDropEntry", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { action: "action", entryCheck: "entryCheck", entryAction: "entryAction", entryMove: "entryMove", lifecycle: "lifecycle" }, ngImport: i0, template: `
|
|
473
|
+
@for (block of blocks(); track $index) {
|
|
474
|
+
@switch (block.type) {
|
|
475
|
+
@case ('text') {
|
|
476
|
+
@if (text(block); as v) {
|
|
477
|
+
<div [ngClass]="classes(block)">
|
|
478
|
+
@if (block.label) { <span class="mkb-label">{{ block.label }}</span> }
|
|
479
|
+
<span class="mkb-text">{{ v }}</span>
|
|
480
|
+
</div>
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
@case ('number') {
|
|
484
|
+
@if (num(block); as v) {
|
|
485
|
+
<div [ngClass]="classes(block)">
|
|
486
|
+
@if (block.label) { <span class="mkb-label">{{ block.label }}</span> }
|
|
487
|
+
<span class="mkb-text">{{ v }}</span>
|
|
488
|
+
</div>
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
@case ('date') {
|
|
492
|
+
@if (dateVal(block); as v) {
|
|
493
|
+
<div [ngClass]="classes(block)">
|
|
494
|
+
@if (block.label) { <span class="mkb-label">{{ block.label }}</span> }
|
|
495
|
+
<span class="mkb-text">{{ v }}</span>
|
|
496
|
+
</div>
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
@case ('time') {
|
|
500
|
+
@if (dateVal(block); as v) {
|
|
501
|
+
<div [ngClass]="classes(block)">
|
|
502
|
+
@if (block.label) { <span class="mkb-label">{{ block.label }}</span> }
|
|
503
|
+
<span class="mkb-text">{{ v }}</span>
|
|
504
|
+
</div>
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
@case ('datetime') {
|
|
508
|
+
@if (dateVal(block); as v) {
|
|
509
|
+
<div [ngClass]="classes(block)">
|
|
510
|
+
@if (block.label) { <span class="mkb-label">{{ block.label }}</span> }
|
|
511
|
+
<span class="mkb-text">{{ v }}</span>
|
|
512
|
+
</div>
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
@case ('image') {
|
|
516
|
+
@if (raw(block.field); as src) {
|
|
517
|
+
<img
|
|
518
|
+
[ngClass]="classes(block)"
|
|
519
|
+
[class.mkb-image-rounded]="block.rounded"
|
|
520
|
+
[src]="src"
|
|
521
|
+
[alt]="block.alt ?? ''"
|
|
522
|
+
loading="lazy"
|
|
523
|
+
/>
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
@case ('tag') {
|
|
527
|
+
@if (raw(block.field) !== undefined && raw(block.field) !== '') {
|
|
528
|
+
<span [ngClass]="classes(block, valueStyle(block).tone ?? block.tone)">
|
|
529
|
+
@if (valueStyle(block).icon; as ic) {
|
|
530
|
+
<span class="mkb-icon" [innerHTML]="icon(ic)"></span>
|
|
531
|
+
}
|
|
532
|
+
{{ valueStyle(block).label ?? raw(block.field) }}
|
|
533
|
+
</span>
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
@case ('badge') {
|
|
537
|
+
@if (raw(block.field) !== undefined && raw(block.field) !== '') {
|
|
538
|
+
<span
|
|
539
|
+
[ngClass]="classes(block, valueStyle(block).tone ?? block.tone)"
|
|
540
|
+
[attr.title]="String(raw(block.field))"
|
|
541
|
+
>
|
|
542
|
+
<span class="mkb-icon" [innerHTML]="icon(valueStyle(block).icon ?? block.icon)"></span>
|
|
543
|
+
@if (block.showText) {
|
|
544
|
+
<span class="mkb-text">{{ valueStyle(block).label ?? raw(block.field) }}</span>
|
|
545
|
+
}
|
|
546
|
+
</span>
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
@case ('icon') {
|
|
550
|
+
<span [ngClass]="classes(block, block.tone)">
|
|
551
|
+
<span class="mkb-icon" [innerHTML]="icon(block.icon)"></span>
|
|
552
|
+
</span>
|
|
553
|
+
}
|
|
554
|
+
@case ('avatar') {
|
|
555
|
+
@if (avatarName(block); as name) {
|
|
556
|
+
<span [ngClass]="classes(block)">
|
|
557
|
+
@if (raw(block.photoField); as photo) {
|
|
558
|
+
<img class="mkb-avatar-img" [src]="photo" [alt]="name" loading="lazy" />
|
|
559
|
+
} @else {
|
|
560
|
+
<span class="mkb-avatar-initials">{{ ini(name) }}</span>
|
|
561
|
+
}
|
|
562
|
+
@if (block.nameField) { <span class="mkb-avatar-name">{{ name }}</span> }
|
|
563
|
+
</span>
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
@case ('facts') {
|
|
567
|
+
<div [ngClass]="classes(block)">
|
|
568
|
+
@for (fact of block.items; track $index) {
|
|
569
|
+
@if (raw(fact.field) !== undefined && raw(fact.field) !== '') {
|
|
570
|
+
<span class="mkb-fact">
|
|
571
|
+
@if (fact.icon) { <span class="mkb-icon" [innerHTML]="icon(fact.icon)"></span> }
|
|
572
|
+
<span class="mkb-fact-value">{{ factText(fact) }}</span>
|
|
573
|
+
</span>
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
</div>
|
|
577
|
+
}
|
|
578
|
+
@case ('items') {
|
|
579
|
+
@if (entryList(block).length) {
|
|
580
|
+
<div [ngClass]="classes(block)" [attr.data-entries]="block.field">
|
|
581
|
+
@if (block.showProgress && block.checkField) {
|
|
582
|
+
<div class="mkb-entries-progress">
|
|
583
|
+
<span class="mkb-entries-count">
|
|
584
|
+
{{ doneCount(block) }}/{{ entryList(block).length }}
|
|
585
|
+
</span>
|
|
586
|
+
<span class="mkb-progress-track">
|
|
587
|
+
<span
|
|
588
|
+
class="mkb-progress-fill"
|
|
589
|
+
[style.width.%]="(doneCount(block) / entryList(block).length) * 100"
|
|
590
|
+
></span>
|
|
591
|
+
</span>
|
|
592
|
+
</div>
|
|
593
|
+
}
|
|
594
|
+
<div
|
|
595
|
+
class="mkb-entries-list"
|
|
596
|
+
cdkDropList
|
|
597
|
+
[id]="entriesId(block)"
|
|
598
|
+
[cdkDropListData]="item().id"
|
|
599
|
+
[cdkDropListConnectedTo]="entryConnections(block)"
|
|
600
|
+
[cdkDropListDisabled]="!reorderEnabled(block)"
|
|
601
|
+
[cdkDropListEnterPredicate]="entryPredicate(block)"
|
|
602
|
+
(cdkDropListDropped)="onEntryDrop(block, $event)"
|
|
603
|
+
>
|
|
604
|
+
@for (entry of visibleEntries(block); track entry.id) {
|
|
605
|
+
<div
|
|
606
|
+
class="mkb-entry"
|
|
607
|
+
cdkDrag
|
|
608
|
+
[cdkDragData]="entry.id"
|
|
609
|
+
[cdkDragDisabled]="!reorderEnabled(block)"
|
|
610
|
+
[attr.data-entry]="entry.id"
|
|
611
|
+
[attr.tabindex]="reorderEnabled(block) ? 0 : null"
|
|
612
|
+
[attr.aria-keyshortcuts]="reorderEnabled(block) ? 'Control+ArrowUp Control+ArrowDown' : null"
|
|
613
|
+
(keydown)="onEntryKeydown(block, entry, $event)"
|
|
614
|
+
(cdkDragStarted)="lifecycle.emit({ phase: 'start', kind: 'entry', id: entry.id })"
|
|
615
|
+
(cdkDragEnded)="lifecycle.emit({ phase: 'end', kind: 'entry', id: entry.id })"
|
|
616
|
+
>
|
|
617
|
+
@if (reorderEnabled(block)) {
|
|
618
|
+
<span class="mkb-entry-grip" cdkDragHandle aria-hidden="true">⠿</span>
|
|
619
|
+
}
|
|
620
|
+
@if (hasCap(block, 'check') && block.checkField) {
|
|
621
|
+
<input
|
|
622
|
+
type="checkbox"
|
|
623
|
+
class="mkb-entry-check"
|
|
624
|
+
[checked]="isDone(block, entry)"
|
|
625
|
+
[attr.aria-label]="entryLabel(block, entry)"
|
|
626
|
+
(change)="toggleEntry(block, entry)"
|
|
627
|
+
/>
|
|
628
|
+
}
|
|
629
|
+
<span class="mkb-entry-label" [class.is-done]="isDone(block, entry)">
|
|
630
|
+
{{ entryLabel(block, entry) }}
|
|
631
|
+
</span>
|
|
632
|
+
@if (hasCap(block, 'action')) {
|
|
633
|
+
@for (action of block.actions ?? []; track action.id) {
|
|
634
|
+
<button
|
|
635
|
+
type="button"
|
|
636
|
+
class="mkb-entry-action"
|
|
637
|
+
[attr.data-entry-action]="action.id"
|
|
638
|
+
(click)="emitEntryAction(block, entry, action.id, $event)"
|
|
639
|
+
>
|
|
640
|
+
{{ action.label }}
|
|
641
|
+
</button>
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
</div>
|
|
645
|
+
}
|
|
646
|
+
</div>
|
|
647
|
+
@if (hiddenCount(block) > 0) {
|
|
648
|
+
<span class="mkb-entries-more">+{{ hiddenCount(block) }}</span>
|
|
649
|
+
}
|
|
650
|
+
</div>
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
@case ('alert') {
|
|
654
|
+
@if (raw(block.field); as v) {
|
|
655
|
+
<div [ngClass]="classes(block, block.tone ?? 'warning')" role="status">
|
|
656
|
+
@if (block.icon) { <span class="mkb-icon" [innerHTML]="icon(block.icon)"></span> }
|
|
657
|
+
<span class="mkb-text">{{ v }}</span>
|
|
658
|
+
</div>
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
@case ('progress') {
|
|
662
|
+
<div [ngClass]="classes(block, block.tone)">
|
|
663
|
+
@if (block.label) { <span class="mkb-label">{{ block.label }}</span> }
|
|
664
|
+
<span
|
|
665
|
+
class="mkb-progress-track"
|
|
666
|
+
role="progressbar"
|
|
667
|
+
[attr.aria-valuenow]="progressValue(block)"
|
|
668
|
+
[attr.aria-valuemax]="block.max ?? 100"
|
|
669
|
+
>
|
|
670
|
+
<span class="mkb-progress-fill" [style.width.%]="progressPct(block)"></span>
|
|
671
|
+
</span>
|
|
672
|
+
</div>
|
|
673
|
+
}
|
|
674
|
+
@case ('divider') {
|
|
675
|
+
<hr [ngClass]="classes(block)" />
|
|
676
|
+
}
|
|
677
|
+
@case ('actions') {
|
|
678
|
+
<div [ngClass]="classes(block)">
|
|
679
|
+
@for (action of visibleActions(block); track action.id) {
|
|
680
|
+
<button
|
|
681
|
+
type="button"
|
|
682
|
+
class="mkb-action"
|
|
683
|
+
[ngClass]="action.tone ? 'mkb-tone-' + action.tone : ''"
|
|
684
|
+
(click)="emitAction(action, $event)"
|
|
685
|
+
>
|
|
686
|
+
@if (action.icon) { <span class="mkb-icon" [innerHTML]="icon(action.icon)"></span> }
|
|
687
|
+
{{ action.label }}
|
|
688
|
+
</button>
|
|
689
|
+
}
|
|
690
|
+
</div>
|
|
691
|
+
}
|
|
692
|
+
@case ('row') {
|
|
693
|
+
<div [ngClass]="rowClasses(block)">
|
|
694
|
+
<mos-kanban-card-blocks
|
|
695
|
+
[blocks]="block.blocks"
|
|
696
|
+
[item]="item()"
|
|
697
|
+
[registry]="registry()"
|
|
698
|
+
[locale]="locale()"
|
|
699
|
+
[column]="column()"
|
|
700
|
+
[lane]="lane()"
|
|
701
|
+
[allCardIds]="allCardIds()"
|
|
702
|
+
[canDropEntry]="canDropEntry()"
|
|
703
|
+
(action)="action.emit($event)"
|
|
704
|
+
(entryCheck)="entryCheck.emit($event)"
|
|
705
|
+
(entryAction)="entryAction.emit($event)"
|
|
706
|
+
(entryMove)="entryMove.emit($event)"
|
|
707
|
+
(lifecycle)="lifecycle.emit($event)"
|
|
708
|
+
/>
|
|
709
|
+
</div>
|
|
710
|
+
}
|
|
711
|
+
@case ('custom') {
|
|
712
|
+
<div [ngClass]="classes(block)" [innerHTML]="customHtml(block.renderer)"></div>
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
`, isInline: true, styles: [":host{display:contents}.mkb-block{min-width:0}.mkb-label{display:block;font-size:10px;font-weight:600;letter-spacing:.3px;text-transform:uppercase;color:var(--mkb-muted, #6f6a7c)}.mkb-block-text .mkb-text,.mkb-block-number .mkb-text{color:var(--mkb-foreground, #241f33);overflow-wrap:anywhere}.mkb-variant-title .mkb-text{font-size:14px;font-weight:600}.mkb-variant-subtitle .mkb-text{font-size:12px;color:var(--mkb-muted, #6f6a7c)}.mkb-variant-caption .mkb-text{font-size:10.5px;color:var(--mkb-muted, #6f6a7c)}.mkb-variant-highlight .mkb-text{font-size:15px;font-weight:700;color:var(--mkb-accent, #5e35b1)}.mkb-block-image{display:block;width:100%;height:auto;object-fit:cover;border-radius:var(--mkb-radius-sm, 6px)}.mkb-image-rounded{border-radius:999px}.mkb-block-tag{display:inline-flex;align-items:center;gap:4px;font-size:10.5px;font-weight:600;padding:1px 8px;border-radius:999px;background:color-mix(in srgb,var(--mkb-tone, var(--mkb-accent, #5e35b1)) 14%,transparent);color:var(--mkb-tone, var(--mkb-accent, #5e35b1))}.mkb-block-badge{display:inline-flex;align-items:center;gap:4px;color:var(--mkb-tone, var(--mkb-muted, #6f6a7c))}.mkb-block-icon{display:inline-flex;color:var(--mkb-tone, var(--mkb-muted, #6f6a7c))}.mkb-block-avatar{display:inline-flex;align-items:center;gap:6px}.mkb-avatar-img,.mkb-avatar-initials{width:24px;height:24px;border-radius:999px;flex:none}.mkb-avatar-initials{display:grid;place-items:center;font-size:10px;font-weight:700;color:#fff;background:var(--mkb-accent, #5e35b1)}.mkb-avatar-name{font-size:12px;color:var(--mkb-foreground, #241f33)}.mkb-block-facts{display:flex;flex-wrap:wrap;gap:var(--mkb-fact-gap, 10px);font-size:11.5px;color:var(--mkb-muted, #6f6a7c)}.mkb-fact{display:inline-flex;align-items:center;gap:3px}.mkb-block-items{display:flex;flex-direction:column;gap:4px}.mkb-entries-progress{display:flex;align-items:center;gap:8px}.mkb-entries-count{font-size:10.5px;font-weight:600;color:var(--mkb-muted, #6f6a7c)}.mkb-entries-progress .mkb-progress-track{flex:1}.mkb-entries-list{display:flex;flex-direction:column;gap:var(--mkb-entry-gap, 4px)}.mkb-entry{display:flex;align-items:center;gap:6px;padding:4px 8px;font-size:12px;color:var(--mkb-foreground, #241f33);background:var(--mkb-column-bg, #efedf3);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius-sm, 6px)}.mkb-entry.cdk-drag-placeholder{opacity:.4;border-style:dashed}.mkb-entry.cdk-drag-preview{box-shadow:0 6px 18px #0003}.mkb-entry-grip{cursor:grab;color:var(--mkb-muted, #6f6a7c);font-size:10px;line-height:1;-webkit-user-select:none;user-select:none}.mkb-entry-check{accent-color:var(--mkb-accent, #5e35b1);margin:0;flex:none}.mkb-entry-label{flex:1;min-width:0;overflow-wrap:anywhere}.mkb-entry-label.is-done{text-decoration:line-through;color:var(--mkb-muted, #6f6a7c)}.mkb-entry-action{padding:2px 8px;border:1px solid var(--mkb-border-strong, #c9c4d4);border-radius:var(--mkb-radius-sm, 6px);background:var(--mkb-card-bg, #fff);color:var(--mkb-foreground, #241f33);font:inherit;font-size:10.5px;cursor:pointer}.mkb-entries-more{font-size:10.5px;color:var(--mkb-muted, #6f6a7c)}.mkb-block-alert{display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:var(--mkb-radius-sm, 6px);font-size:11.5px;background:color-mix(in srgb,var(--mkb-tone, var(--mkb-warning, #9a6b00)) 14%,transparent);color:var(--mkb-tone, var(--mkb-warning, #9a6b00))}.mkb-block-progress{display:flex;flex-direction:column;gap:3px}.mkb-progress-track{display:block;height:6px;border-radius:999px;background:var(--mkb-border, #e3e0ea);overflow:hidden}.mkb-progress-fill{display:block;height:100%;border-radius:999px;background:var(--mkb-tone, var(--mkb-accent, #5e35b1))}.mkb-block-divider{border:0;border-top:1px solid var(--mkb-border, #e3e0ea);margin:2px 0}.mkb-block-actions{display:flex;flex-wrap:wrap;gap:6px}.mkb-action{display:inline-flex;align-items:center;gap:4px;padding:5px 10px;border:1px solid var(--mkb-border-strong, #c9c4d4);border-radius:var(--mkb-radius-sm, 6px);background:var(--mkb-card-bg, #fff);color:var(--mkb-tone, var(--mkb-foreground, #241f33));font:inherit;font-size:12px;cursor:pointer}.mkb-action:hover{border-color:var(--mkb-tone, var(--mkb-accent, #5e35b1))}.mkb-block-row{display:flex;align-items:center;gap:8px}.mkb-row-between{justify-content:space-between}.mkb-row-center{justify-content:center}.mkb-row-end{justify-content:flex-end}.mkb-row-wrap{flex-wrap:wrap}.mkb-icon{display:inline-flex;align-items:center}.mkb-tone-info{--mkb-tone: var(--mkb-info, #2264c4)}.mkb-tone-success{--mkb-tone: var(--mkb-success, #1b6e3c)}.mkb-tone-warning{--mkb-tone: var(--mkb-warning, #9a6b00)}.mkb-tone-danger{--mkb-tone: var(--mkb-danger, #b3261e)}.mkb-tone-neutral{--mkb-tone: var(--mkb-muted, #6f6a7c)}\n"], dependencies: [{ kind: "component", type: MosKanbanCardBlocks, selector: "mos-kanban-card-blocks", inputs: ["blocks", "item", "registry", "locale", "column", "lane", "allCardIds", "canDropEntry"], outputs: ["action", "entryCheck", "entryAction", "entryMove", "lifecycle"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: 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: 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: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
717
|
+
}
|
|
718
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MosKanbanCardBlocks, decorators: [{
|
|
719
|
+
type: Component,
|
|
720
|
+
args: [{ selector: 'mos-kanban-card-blocks', changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgClass, CdkDropList, CdkDrag, CdkDragHandle, MosKanbanCardBlocks], template: `
|
|
721
|
+
@for (block of blocks(); track $index) {
|
|
722
|
+
@switch (block.type) {
|
|
723
|
+
@case ('text') {
|
|
724
|
+
@if (text(block); as v) {
|
|
725
|
+
<div [ngClass]="classes(block)">
|
|
726
|
+
@if (block.label) { <span class="mkb-label">{{ block.label }}</span> }
|
|
727
|
+
<span class="mkb-text">{{ v }}</span>
|
|
728
|
+
</div>
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
@case ('number') {
|
|
732
|
+
@if (num(block); as v) {
|
|
733
|
+
<div [ngClass]="classes(block)">
|
|
734
|
+
@if (block.label) { <span class="mkb-label">{{ block.label }}</span> }
|
|
735
|
+
<span class="mkb-text">{{ v }}</span>
|
|
736
|
+
</div>
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
@case ('date') {
|
|
740
|
+
@if (dateVal(block); as v) {
|
|
741
|
+
<div [ngClass]="classes(block)">
|
|
742
|
+
@if (block.label) { <span class="mkb-label">{{ block.label }}</span> }
|
|
743
|
+
<span class="mkb-text">{{ v }}</span>
|
|
744
|
+
</div>
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
@case ('time') {
|
|
748
|
+
@if (dateVal(block); as v) {
|
|
749
|
+
<div [ngClass]="classes(block)">
|
|
750
|
+
@if (block.label) { <span class="mkb-label">{{ block.label }}</span> }
|
|
751
|
+
<span class="mkb-text">{{ v }}</span>
|
|
752
|
+
</div>
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
@case ('datetime') {
|
|
756
|
+
@if (dateVal(block); as v) {
|
|
757
|
+
<div [ngClass]="classes(block)">
|
|
758
|
+
@if (block.label) { <span class="mkb-label">{{ block.label }}</span> }
|
|
759
|
+
<span class="mkb-text">{{ v }}</span>
|
|
760
|
+
</div>
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
@case ('image') {
|
|
764
|
+
@if (raw(block.field); as src) {
|
|
765
|
+
<img
|
|
766
|
+
[ngClass]="classes(block)"
|
|
767
|
+
[class.mkb-image-rounded]="block.rounded"
|
|
768
|
+
[src]="src"
|
|
769
|
+
[alt]="block.alt ?? ''"
|
|
770
|
+
loading="lazy"
|
|
771
|
+
/>
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
@case ('tag') {
|
|
775
|
+
@if (raw(block.field) !== undefined && raw(block.field) !== '') {
|
|
776
|
+
<span [ngClass]="classes(block, valueStyle(block).tone ?? block.tone)">
|
|
777
|
+
@if (valueStyle(block).icon; as ic) {
|
|
778
|
+
<span class="mkb-icon" [innerHTML]="icon(ic)"></span>
|
|
779
|
+
}
|
|
780
|
+
{{ valueStyle(block).label ?? raw(block.field) }}
|
|
781
|
+
</span>
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
@case ('badge') {
|
|
785
|
+
@if (raw(block.field) !== undefined && raw(block.field) !== '') {
|
|
786
|
+
<span
|
|
787
|
+
[ngClass]="classes(block, valueStyle(block).tone ?? block.tone)"
|
|
788
|
+
[attr.title]="String(raw(block.field))"
|
|
789
|
+
>
|
|
790
|
+
<span class="mkb-icon" [innerHTML]="icon(valueStyle(block).icon ?? block.icon)"></span>
|
|
791
|
+
@if (block.showText) {
|
|
792
|
+
<span class="mkb-text">{{ valueStyle(block).label ?? raw(block.field) }}</span>
|
|
793
|
+
}
|
|
794
|
+
</span>
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
@case ('icon') {
|
|
798
|
+
<span [ngClass]="classes(block, block.tone)">
|
|
799
|
+
<span class="mkb-icon" [innerHTML]="icon(block.icon)"></span>
|
|
800
|
+
</span>
|
|
801
|
+
}
|
|
802
|
+
@case ('avatar') {
|
|
803
|
+
@if (avatarName(block); as name) {
|
|
804
|
+
<span [ngClass]="classes(block)">
|
|
805
|
+
@if (raw(block.photoField); as photo) {
|
|
806
|
+
<img class="mkb-avatar-img" [src]="photo" [alt]="name" loading="lazy" />
|
|
807
|
+
} @else {
|
|
808
|
+
<span class="mkb-avatar-initials">{{ ini(name) }}</span>
|
|
809
|
+
}
|
|
810
|
+
@if (block.nameField) { <span class="mkb-avatar-name">{{ name }}</span> }
|
|
811
|
+
</span>
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
@case ('facts') {
|
|
815
|
+
<div [ngClass]="classes(block)">
|
|
816
|
+
@for (fact of block.items; track $index) {
|
|
817
|
+
@if (raw(fact.field) !== undefined && raw(fact.field) !== '') {
|
|
818
|
+
<span class="mkb-fact">
|
|
819
|
+
@if (fact.icon) { <span class="mkb-icon" [innerHTML]="icon(fact.icon)"></span> }
|
|
820
|
+
<span class="mkb-fact-value">{{ factText(fact) }}</span>
|
|
821
|
+
</span>
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
</div>
|
|
825
|
+
}
|
|
826
|
+
@case ('items') {
|
|
827
|
+
@if (entryList(block).length) {
|
|
828
|
+
<div [ngClass]="classes(block)" [attr.data-entries]="block.field">
|
|
829
|
+
@if (block.showProgress && block.checkField) {
|
|
830
|
+
<div class="mkb-entries-progress">
|
|
831
|
+
<span class="mkb-entries-count">
|
|
832
|
+
{{ doneCount(block) }}/{{ entryList(block).length }}
|
|
833
|
+
</span>
|
|
834
|
+
<span class="mkb-progress-track">
|
|
835
|
+
<span
|
|
836
|
+
class="mkb-progress-fill"
|
|
837
|
+
[style.width.%]="(doneCount(block) / entryList(block).length) * 100"
|
|
838
|
+
></span>
|
|
839
|
+
</span>
|
|
840
|
+
</div>
|
|
841
|
+
}
|
|
842
|
+
<div
|
|
843
|
+
class="mkb-entries-list"
|
|
844
|
+
cdkDropList
|
|
845
|
+
[id]="entriesId(block)"
|
|
846
|
+
[cdkDropListData]="item().id"
|
|
847
|
+
[cdkDropListConnectedTo]="entryConnections(block)"
|
|
848
|
+
[cdkDropListDisabled]="!reorderEnabled(block)"
|
|
849
|
+
[cdkDropListEnterPredicate]="entryPredicate(block)"
|
|
850
|
+
(cdkDropListDropped)="onEntryDrop(block, $event)"
|
|
851
|
+
>
|
|
852
|
+
@for (entry of visibleEntries(block); track entry.id) {
|
|
853
|
+
<div
|
|
854
|
+
class="mkb-entry"
|
|
855
|
+
cdkDrag
|
|
856
|
+
[cdkDragData]="entry.id"
|
|
857
|
+
[cdkDragDisabled]="!reorderEnabled(block)"
|
|
858
|
+
[attr.data-entry]="entry.id"
|
|
859
|
+
[attr.tabindex]="reorderEnabled(block) ? 0 : null"
|
|
860
|
+
[attr.aria-keyshortcuts]="reorderEnabled(block) ? 'Control+ArrowUp Control+ArrowDown' : null"
|
|
861
|
+
(keydown)="onEntryKeydown(block, entry, $event)"
|
|
862
|
+
(cdkDragStarted)="lifecycle.emit({ phase: 'start', kind: 'entry', id: entry.id })"
|
|
863
|
+
(cdkDragEnded)="lifecycle.emit({ phase: 'end', kind: 'entry', id: entry.id })"
|
|
864
|
+
>
|
|
865
|
+
@if (reorderEnabled(block)) {
|
|
866
|
+
<span class="mkb-entry-grip" cdkDragHandle aria-hidden="true">⠿</span>
|
|
867
|
+
}
|
|
868
|
+
@if (hasCap(block, 'check') && block.checkField) {
|
|
869
|
+
<input
|
|
870
|
+
type="checkbox"
|
|
871
|
+
class="mkb-entry-check"
|
|
872
|
+
[checked]="isDone(block, entry)"
|
|
873
|
+
[attr.aria-label]="entryLabel(block, entry)"
|
|
874
|
+
(change)="toggleEntry(block, entry)"
|
|
875
|
+
/>
|
|
876
|
+
}
|
|
877
|
+
<span class="mkb-entry-label" [class.is-done]="isDone(block, entry)">
|
|
878
|
+
{{ entryLabel(block, entry) }}
|
|
879
|
+
</span>
|
|
880
|
+
@if (hasCap(block, 'action')) {
|
|
881
|
+
@for (action of block.actions ?? []; track action.id) {
|
|
882
|
+
<button
|
|
883
|
+
type="button"
|
|
884
|
+
class="mkb-entry-action"
|
|
885
|
+
[attr.data-entry-action]="action.id"
|
|
886
|
+
(click)="emitEntryAction(block, entry, action.id, $event)"
|
|
887
|
+
>
|
|
888
|
+
{{ action.label }}
|
|
889
|
+
</button>
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
</div>
|
|
893
|
+
}
|
|
894
|
+
</div>
|
|
895
|
+
@if (hiddenCount(block) > 0) {
|
|
896
|
+
<span class="mkb-entries-more">+{{ hiddenCount(block) }}</span>
|
|
897
|
+
}
|
|
898
|
+
</div>
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
@case ('alert') {
|
|
902
|
+
@if (raw(block.field); as v) {
|
|
903
|
+
<div [ngClass]="classes(block, block.tone ?? 'warning')" role="status">
|
|
904
|
+
@if (block.icon) { <span class="mkb-icon" [innerHTML]="icon(block.icon)"></span> }
|
|
905
|
+
<span class="mkb-text">{{ v }}</span>
|
|
906
|
+
</div>
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
@case ('progress') {
|
|
910
|
+
<div [ngClass]="classes(block, block.tone)">
|
|
911
|
+
@if (block.label) { <span class="mkb-label">{{ block.label }}</span> }
|
|
912
|
+
<span
|
|
913
|
+
class="mkb-progress-track"
|
|
914
|
+
role="progressbar"
|
|
915
|
+
[attr.aria-valuenow]="progressValue(block)"
|
|
916
|
+
[attr.aria-valuemax]="block.max ?? 100"
|
|
917
|
+
>
|
|
918
|
+
<span class="mkb-progress-fill" [style.width.%]="progressPct(block)"></span>
|
|
919
|
+
</span>
|
|
920
|
+
</div>
|
|
921
|
+
}
|
|
922
|
+
@case ('divider') {
|
|
923
|
+
<hr [ngClass]="classes(block)" />
|
|
924
|
+
}
|
|
925
|
+
@case ('actions') {
|
|
926
|
+
<div [ngClass]="classes(block)">
|
|
927
|
+
@for (action of visibleActions(block); track action.id) {
|
|
928
|
+
<button
|
|
929
|
+
type="button"
|
|
930
|
+
class="mkb-action"
|
|
931
|
+
[ngClass]="action.tone ? 'mkb-tone-' + action.tone : ''"
|
|
932
|
+
(click)="emitAction(action, $event)"
|
|
933
|
+
>
|
|
934
|
+
@if (action.icon) { <span class="mkb-icon" [innerHTML]="icon(action.icon)"></span> }
|
|
935
|
+
{{ action.label }}
|
|
936
|
+
</button>
|
|
937
|
+
}
|
|
938
|
+
</div>
|
|
939
|
+
}
|
|
940
|
+
@case ('row') {
|
|
941
|
+
<div [ngClass]="rowClasses(block)">
|
|
942
|
+
<mos-kanban-card-blocks
|
|
943
|
+
[blocks]="block.blocks"
|
|
944
|
+
[item]="item()"
|
|
945
|
+
[registry]="registry()"
|
|
946
|
+
[locale]="locale()"
|
|
947
|
+
[column]="column()"
|
|
948
|
+
[lane]="lane()"
|
|
949
|
+
[allCardIds]="allCardIds()"
|
|
950
|
+
[canDropEntry]="canDropEntry()"
|
|
951
|
+
(action)="action.emit($event)"
|
|
952
|
+
(entryCheck)="entryCheck.emit($event)"
|
|
953
|
+
(entryAction)="entryAction.emit($event)"
|
|
954
|
+
(entryMove)="entryMove.emit($event)"
|
|
955
|
+
(lifecycle)="lifecycle.emit($event)"
|
|
956
|
+
/>
|
|
957
|
+
</div>
|
|
958
|
+
}
|
|
959
|
+
@case ('custom') {
|
|
960
|
+
<div [ngClass]="classes(block)" [innerHTML]="customHtml(block.renderer)"></div>
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
`, styles: [":host{display:contents}.mkb-block{min-width:0}.mkb-label{display:block;font-size:10px;font-weight:600;letter-spacing:.3px;text-transform:uppercase;color:var(--mkb-muted, #6f6a7c)}.mkb-block-text .mkb-text,.mkb-block-number .mkb-text{color:var(--mkb-foreground, #241f33);overflow-wrap:anywhere}.mkb-variant-title .mkb-text{font-size:14px;font-weight:600}.mkb-variant-subtitle .mkb-text{font-size:12px;color:var(--mkb-muted, #6f6a7c)}.mkb-variant-caption .mkb-text{font-size:10.5px;color:var(--mkb-muted, #6f6a7c)}.mkb-variant-highlight .mkb-text{font-size:15px;font-weight:700;color:var(--mkb-accent, #5e35b1)}.mkb-block-image{display:block;width:100%;height:auto;object-fit:cover;border-radius:var(--mkb-radius-sm, 6px)}.mkb-image-rounded{border-radius:999px}.mkb-block-tag{display:inline-flex;align-items:center;gap:4px;font-size:10.5px;font-weight:600;padding:1px 8px;border-radius:999px;background:color-mix(in srgb,var(--mkb-tone, var(--mkb-accent, #5e35b1)) 14%,transparent);color:var(--mkb-tone, var(--mkb-accent, #5e35b1))}.mkb-block-badge{display:inline-flex;align-items:center;gap:4px;color:var(--mkb-tone, var(--mkb-muted, #6f6a7c))}.mkb-block-icon{display:inline-flex;color:var(--mkb-tone, var(--mkb-muted, #6f6a7c))}.mkb-block-avatar{display:inline-flex;align-items:center;gap:6px}.mkb-avatar-img,.mkb-avatar-initials{width:24px;height:24px;border-radius:999px;flex:none}.mkb-avatar-initials{display:grid;place-items:center;font-size:10px;font-weight:700;color:#fff;background:var(--mkb-accent, #5e35b1)}.mkb-avatar-name{font-size:12px;color:var(--mkb-foreground, #241f33)}.mkb-block-facts{display:flex;flex-wrap:wrap;gap:var(--mkb-fact-gap, 10px);font-size:11.5px;color:var(--mkb-muted, #6f6a7c)}.mkb-fact{display:inline-flex;align-items:center;gap:3px}.mkb-block-items{display:flex;flex-direction:column;gap:4px}.mkb-entries-progress{display:flex;align-items:center;gap:8px}.mkb-entries-count{font-size:10.5px;font-weight:600;color:var(--mkb-muted, #6f6a7c)}.mkb-entries-progress .mkb-progress-track{flex:1}.mkb-entries-list{display:flex;flex-direction:column;gap:var(--mkb-entry-gap, 4px)}.mkb-entry{display:flex;align-items:center;gap:6px;padding:4px 8px;font-size:12px;color:var(--mkb-foreground, #241f33);background:var(--mkb-column-bg, #efedf3);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius-sm, 6px)}.mkb-entry.cdk-drag-placeholder{opacity:.4;border-style:dashed}.mkb-entry.cdk-drag-preview{box-shadow:0 6px 18px #0003}.mkb-entry-grip{cursor:grab;color:var(--mkb-muted, #6f6a7c);font-size:10px;line-height:1;-webkit-user-select:none;user-select:none}.mkb-entry-check{accent-color:var(--mkb-accent, #5e35b1);margin:0;flex:none}.mkb-entry-label{flex:1;min-width:0;overflow-wrap:anywhere}.mkb-entry-label.is-done{text-decoration:line-through;color:var(--mkb-muted, #6f6a7c)}.mkb-entry-action{padding:2px 8px;border:1px solid var(--mkb-border-strong, #c9c4d4);border-radius:var(--mkb-radius-sm, 6px);background:var(--mkb-card-bg, #fff);color:var(--mkb-foreground, #241f33);font:inherit;font-size:10.5px;cursor:pointer}.mkb-entries-more{font-size:10.5px;color:var(--mkb-muted, #6f6a7c)}.mkb-block-alert{display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:var(--mkb-radius-sm, 6px);font-size:11.5px;background:color-mix(in srgb,var(--mkb-tone, var(--mkb-warning, #9a6b00)) 14%,transparent);color:var(--mkb-tone, var(--mkb-warning, #9a6b00))}.mkb-block-progress{display:flex;flex-direction:column;gap:3px}.mkb-progress-track{display:block;height:6px;border-radius:999px;background:var(--mkb-border, #e3e0ea);overflow:hidden}.mkb-progress-fill{display:block;height:100%;border-radius:999px;background:var(--mkb-tone, var(--mkb-accent, #5e35b1))}.mkb-block-divider{border:0;border-top:1px solid var(--mkb-border, #e3e0ea);margin:2px 0}.mkb-block-actions{display:flex;flex-wrap:wrap;gap:6px}.mkb-action{display:inline-flex;align-items:center;gap:4px;padding:5px 10px;border:1px solid var(--mkb-border-strong, #c9c4d4);border-radius:var(--mkb-radius-sm, 6px);background:var(--mkb-card-bg, #fff);color:var(--mkb-tone, var(--mkb-foreground, #241f33));font:inherit;font-size:12px;cursor:pointer}.mkb-action:hover{border-color:var(--mkb-tone, var(--mkb-accent, #5e35b1))}.mkb-block-row{display:flex;align-items:center;gap:8px}.mkb-row-between{justify-content:space-between}.mkb-row-center{justify-content:center}.mkb-row-end{justify-content:flex-end}.mkb-row-wrap{flex-wrap:wrap}.mkb-icon{display:inline-flex;align-items:center}.mkb-tone-info{--mkb-tone: var(--mkb-info, #2264c4)}.mkb-tone-success{--mkb-tone: var(--mkb-success, #1b6e3c)}.mkb-tone-warning{--mkb-tone: var(--mkb-warning, #9a6b00)}.mkb-tone-danger{--mkb-tone: var(--mkb-danger, #b3261e)}.mkb-tone-neutral{--mkb-tone: var(--mkb-muted, #6f6a7c)}\n"] }]
|
|
965
|
+
}], propDecorators: { blocks: [{ type: i0.Input, args: [{ isSignal: true, alias: "blocks", required: true }] }], item: [{ type: i0.Input, args: [{ isSignal: true, alias: "item", required: true }] }], registry: [{ type: i0.Input, args: [{ isSignal: true, alias: "registry", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: false }] }], lane: [{ type: i0.Input, args: [{ isSignal: true, alias: "lane", required: false }] }], allCardIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "allCardIds", required: false }] }], canDropEntry: [{ type: i0.Input, args: [{ isSignal: true, alias: "canDropEntry", required: false }] }], action: [{ type: i0.Output, args: ["action"] }], entryCheck: [{ type: i0.Output, args: ["entryCheck"] }], entryAction: [{ type: i0.Output, args: ["entryAction"] }], entryMove: [{ type: i0.Output, args: ["entryMove"] }], lifecycle: [{ type: i0.Output, args: ["lifecycle"] }] } });
|
|
966
|
+
|
|
967
|
+
/**
|
|
968
|
+
* Uma CÉLULA do board (coluna × lane; sem lanes, a coluna inteira): drop list
|
|
969
|
+
* do CDK + cartões. É a unidade de drop — toda decisão de movimento é
|
|
970
|
+
* delegada ao core via drag-controller (preview == commit).
|
|
971
|
+
*/
|
|
972
|
+
class MosKanbanCell {
|
|
973
|
+
board = input.required(...(ngDevMode ? [{ debugName: "board" }] : /* istanbul ignore next */ []));
|
|
974
|
+
columnId = input.required(...(ngDevMode ? [{ debugName: "columnId" }] : /* istanbul ignore next */ []));
|
|
975
|
+
laneId = input(undefined, ...(ngDevMode ? [{ debugName: "laneId" }] : /* istanbul ignore next */ []));
|
|
976
|
+
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
977
|
+
cardTemplate = input(null, ...(ngDevMode ? [{ debugName: "cardTemplate" }] : /* istanbul ignore next */ []));
|
|
978
|
+
/** Modelo declarativo do cartão (do board.cardConfig); usado quando não há
|
|
979
|
+
* template custom. */
|
|
980
|
+
cardConfig = input(null, ...(ngDevMode ? [{ debugName: "cardConfig" }] : /* istanbul ignore next */ []));
|
|
981
|
+
readOnly = input(false, ...(ngDevMode ? [{ debugName: "readOnly" }] : /* istanbul ignore next */ []));
|
|
982
|
+
selection = input([], ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
|
|
983
|
+
/** Ids das drop lists de células conectadas (fornecidos pelo board). */
|
|
984
|
+
connectedTo = input([], ...(ngDevMode ? [{ debugName: "connectedTo" }] : /* istanbul ignore next */ []));
|
|
985
|
+
/** Ids de todos os cards (conexões do move-across de entries). */
|
|
986
|
+
allCardIds = input([], ...(ngDevMode ? [{ debugName: "allCardIds" }] : /* istanbul ignore next */ []));
|
|
987
|
+
cardActivate = output();
|
|
988
|
+
cardContext = output();
|
|
989
|
+
cardKey = output();
|
|
990
|
+
cardAction = output();
|
|
991
|
+
entryCheck = output();
|
|
992
|
+
entryAction = output();
|
|
993
|
+
entryMove = output();
|
|
994
|
+
/** Ciclo de gesto (card/entry) — o board traduz em dragStarted/Ended. */
|
|
995
|
+
lifecycle = output();
|
|
996
|
+
dropRejected = output();
|
|
997
|
+
/** Preview de drop de entry entre cards — MESMO caminho do commit. */
|
|
998
|
+
canDropEntryFn = (req) => this.board().canMoveEntry({
|
|
999
|
+
cardId: req.fromCardId,
|
|
1000
|
+
field: req.field,
|
|
1001
|
+
entryId: req.entryId,
|
|
1002
|
+
index: 0,
|
|
1003
|
+
toCardId: req.toCardId,
|
|
1004
|
+
}).ok;
|
|
1005
|
+
/** Toque segura 250ms antes de iniciar o drag — o scroll da lista continua
|
|
1006
|
+
* natural no touch; mouse arrasta imediatamente. */
|
|
1007
|
+
dragStartDelay = { touch: 250, mouse: 0 };
|
|
1008
|
+
/** Id DOM da própria drop list (conectada às demais pelo board). */
|
|
1009
|
+
cellId = computed(() => cellListId(this.columnId(), this.laneId()), ...(ngDevMode ? [{ debugName: "cellId" }] : /* istanbul ignore next */ []));
|
|
1010
|
+
label() {
|
|
1011
|
+
const lane = this.laneId();
|
|
1012
|
+
return lane ? `${this.columnId()} · ${lane}` : this.columnId();
|
|
1013
|
+
}
|
|
1014
|
+
/** O CDK pergunta se o item pode ENTRAR — a resposta vem do core. Drags de
|
|
1015
|
+
* grupo (coluna/lane, data = objeto) NÃO entram em célula: só cartões
|
|
1016
|
+
* (data = id string). */
|
|
1017
|
+
enterPredicate = (drag) => typeof drag.data === 'string' &&
|
|
1018
|
+
canEnterCell(this.board(), drag.data, this.columnId(), this.laneId());
|
|
1019
|
+
/** Flag de cancelamento por Esc (provider do board; null fora dele). */
|
|
1020
|
+
cancelFlag = inject(DragCancelFlag, { optional: true });
|
|
1021
|
+
onDrop(event) {
|
|
1022
|
+
if (this.cancelFlag?.consume())
|
|
1023
|
+
return; // Esc: gesto cancelado
|
|
1024
|
+
const itemId = event.item.data;
|
|
1025
|
+
if (event.previousContainer === event.container &&
|
|
1026
|
+
event.previousIndex === event.currentIndex) {
|
|
1027
|
+
return; // soltou onde estava
|
|
1028
|
+
}
|
|
1029
|
+
const result = executeDrop(this.board(), itemId, this.columnId(), event.currentIndex, this.laneId());
|
|
1030
|
+
if (!result.ok) {
|
|
1031
|
+
this.dropRejected.emit({
|
|
1032
|
+
itemId,
|
|
1033
|
+
columnId: this.columnId(),
|
|
1034
|
+
laneId: this.laneId(),
|
|
1035
|
+
reason: result.reason,
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
activate(item, event) {
|
|
1040
|
+
const toggle = event instanceof MouseEvent && (event.ctrlKey || event.metaKey);
|
|
1041
|
+
this.cardActivate.emit({ item, toggle });
|
|
1042
|
+
}
|
|
1043
|
+
onKeydown(item, event) {
|
|
1044
|
+
if (event.key === 'Enter') {
|
|
1045
|
+
this.cardActivate.emit({ item, toggle: event.ctrlKey || event.metaKey });
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
if (event.key === 'ArrowUp' ||
|
|
1049
|
+
event.key === 'ArrowDown' ||
|
|
1050
|
+
event.key === 'ArrowLeft' ||
|
|
1051
|
+
event.key === 'ArrowRight') {
|
|
1052
|
+
event.preventDefault(); // senão a seta rola a lista
|
|
1053
|
+
this.cardKey.emit({
|
|
1054
|
+
item,
|
|
1055
|
+
key: event.key,
|
|
1056
|
+
ctrl: event.ctrlKey || event.metaKey,
|
|
1057
|
+
shift: event.shiftKey,
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
onContextMenu(item, event) {
|
|
1062
|
+
const mouse = event;
|
|
1063
|
+
event.preventDefault();
|
|
1064
|
+
this.cardContext.emit({ item, x: mouse.clientX, y: mouse.clientY });
|
|
1065
|
+
}
|
|
1066
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MosKanbanCell, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1067
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: MosKanbanCell, isStandalone: true, selector: "mos-kanban-cell", inputs: { board: { classPropertyName: "board", publicName: "board", isSignal: true, isRequired: true, transformFunction: null }, columnId: { classPropertyName: "columnId", publicName: "columnId", isSignal: true, isRequired: true, transformFunction: null }, laneId: { classPropertyName: "laneId", publicName: "laneId", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, cardTemplate: { classPropertyName: "cardTemplate", publicName: "cardTemplate", isSignal: true, isRequired: false, transformFunction: null }, cardConfig: { classPropertyName: "cardConfig", publicName: "cardConfig", isSignal: true, isRequired: false, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, connectedTo: { classPropertyName: "connectedTo", publicName: "connectedTo", isSignal: true, isRequired: false, transformFunction: null }, allCardIds: { classPropertyName: "allCardIds", publicName: "allCardIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cardActivate: "cardActivate", cardContext: "cardContext", cardKey: "cardKey", cardAction: "cardAction", entryCheck: "entryCheck", entryAction: "entryAction", entryMove: "entryMove", lifecycle: "lifecycle", dropRejected: "dropRejected" }, ngImport: i0, template: `
|
|
1068
|
+
<div
|
|
1069
|
+
class="mkb-cell-body"
|
|
1070
|
+
cdkDropList
|
|
1071
|
+
[id]="cellId()"
|
|
1072
|
+
[cdkDropListConnectedTo]="connectedTo()"
|
|
1073
|
+
[cdkDropListDisabled]="readOnly()"
|
|
1074
|
+
[cdkDropListEnterPredicate]="enterPredicate"
|
|
1075
|
+
(cdkDropListDropped)="onDrop($event)"
|
|
1076
|
+
role="listbox"
|
|
1077
|
+
aria-multiselectable="true"
|
|
1078
|
+
[attr.aria-label]="label()"
|
|
1079
|
+
[attr.data-column]="columnId()"
|
|
1080
|
+
[attr.data-lane]="laneId() ?? null"
|
|
1081
|
+
>
|
|
1082
|
+
@for (item of items(); track item.id) {
|
|
1083
|
+
<div
|
|
1084
|
+
class="mkb-card"
|
|
1085
|
+
cdkDrag
|
|
1086
|
+
[cdkDragData]="item.id"
|
|
1087
|
+
[cdkDragDisabled]="readOnly()"
|
|
1088
|
+
[cdkDragStartDelay]="dragStartDelay"
|
|
1089
|
+
role="option"
|
|
1090
|
+
tabindex="0"
|
|
1091
|
+
[class.is-selected]="selection().includes(item.id)"
|
|
1092
|
+
[attr.data-item]="item.id"
|
|
1093
|
+
[attr.aria-selected]="selection().includes(item.id)"
|
|
1094
|
+
aria-keyshortcuts="Control+ArrowLeft Control+ArrowRight Control+ArrowUp Control+ArrowDown"
|
|
1095
|
+
(click)="activate(item, $event)"
|
|
1096
|
+
(keydown)="onKeydown(item, $event)"
|
|
1097
|
+
(contextmenu)="onContextMenu(item, $event)"
|
|
1098
|
+
(cdkDragStarted)="lifecycle.emit({ phase: 'start', kind: 'card', id: item.id })"
|
|
1099
|
+
(cdkDragEnded)="lifecycle.emit({ phase: 'end', kind: 'card', id: item.id })"
|
|
1100
|
+
>
|
|
1101
|
+
@if (cardConfig()?.dragHandle && !readOnly()) {
|
|
1102
|
+
<span class="mkb-card-grip" cdkDragHandle aria-hidden="true">⠿</span>
|
|
1103
|
+
}
|
|
1104
|
+
@if (cardTemplate(); as tpl) {
|
|
1105
|
+
<ng-container *ngTemplateOutlet="tpl; context: { $implicit: item }" />
|
|
1106
|
+
} @else if (cardConfig(); as card) {
|
|
1107
|
+
<mos-kanban-card-blocks
|
|
1108
|
+
[blocks]="card.blocks"
|
|
1109
|
+
[item]="item"
|
|
1110
|
+
[registry]="board().registry"
|
|
1111
|
+
[locale]="board().locale"
|
|
1112
|
+
[column]="columnId()"
|
|
1113
|
+
[lane]="laneId()"
|
|
1114
|
+
[allCardIds]="allCardIds()"
|
|
1115
|
+
[canDropEntry]="canDropEntryFn"
|
|
1116
|
+
(action)="cardAction.emit($event)"
|
|
1117
|
+
(entryCheck)="entryCheck.emit($event)"
|
|
1118
|
+
(entryAction)="entryAction.emit($event)"
|
|
1119
|
+
(entryMove)="entryMove.emit($event)"
|
|
1120
|
+
(lifecycle)="lifecycle.emit($event)"
|
|
1121
|
+
/>
|
|
1122
|
+
} @else {
|
|
1123
|
+
<mos-kanban-card [item]="item" />
|
|
1124
|
+
}
|
|
1125
|
+
</div>
|
|
1126
|
+
} @empty {
|
|
1127
|
+
<div class="mkb-cell-empty">Solte itens aqui</div>
|
|
1128
|
+
}
|
|
1129
|
+
</div>
|
|
1130
|
+
`, isInline: true, styles: [":host{display:block;min-width:0}.mkb-cell-body{display:flex;flex-direction:column;gap:var(--mkb-card-gap, 8px);padding:var(--mkb-cell-padding, 4px 8px 10px);overflow-y:auto;min-height:60px;height:100%}.mkb-card{position:relative;display:flex;flex-direction:column;gap:var(--mkb-block-gap, 8px);background:var(--mkb-card-bg, var(--color-panel, #fff));border:1px solid var(--mkb-border, var(--color-border, #e3e0ea));border-radius:var(--mkb-radius-sm, var(--radius-sm, 6px));padding:var(--mkb-card-padding, 10px 12px);cursor:grab;-webkit-tap-highlight-color:transparent;transition:box-shadow var(--mkb-dur, .15s) var(--mkb-ease, ease),border-color var(--mkb-dur, .15s) var(--mkb-ease, ease)}.mkb-card.is-selected{border-color:var(--mkb-accent, #5e35b1);box-shadow:0 0 0 1px var(--mkb-accent, #5e35b1)}.mkb-card:focus-visible{outline:none;box-shadow:var(--mkb-focus-ring, 0 0 0 3px rgba(94, 53, 177, .35))}.mkb-card.cdk-drag-dragging{cursor:grabbing}.mkb-card-grip{position:absolute;top:8px;right:8px;cursor:grab;color:var(--mkb-muted, #6f6a7c);font-size:11px;line-height:1;-webkit-user-select:none;user-select:none}.mkb-card:has(.mkb-card-grip){cursor:default}.mkb-card.cdk-drag-preview{box-shadow:0 10px 28px #00000038;rotate:2deg;opacity:.95}.mkb-card.cdk-drag-animating{transition:transform var(--mkb-dur-move, .18s) var(--mkb-ease, ease)}@media(prefers-reduced-motion:reduce){.mkb-card,.mkb-card.cdk-drag-animating{transition:none}.mkb-card.cdk-drag-preview{rotate:none}}.mkb-card.cdk-drag-placeholder{opacity:.4;border:1px dashed var(--mkb-accent, #5e35b1);background:color-mix(in srgb,var(--mkb-accent, #5e35b1) 6%,var(--mkb-card-bg, #fff))}.mkb-cell-empty{padding:14px 8px;text-align:center;font-size:11.5px;color:var(--mkb-muted, var(--color-muted, #6f6a7c));border:1px dashed var(--mkb-border-strong, #c9c4d4);border-radius:var(--mkb-radius-sm, 6px)}:host-context(.mkb-readonly) .mkb-card{cursor:default}\n"], dependencies: [{ kind: "directive", type: 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: 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: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: MosKanbanCard, selector: "mos-kanban-card", inputs: ["item"] }, { kind: "component", type: MosKanbanCardBlocks, selector: "mos-kanban-card-blocks", inputs: ["blocks", "item", "registry", "locale", "column", "lane", "allCardIds", "canDropEntry"], outputs: ["action", "entryCheck", "entryAction", "entryMove", "lifecycle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1131
|
+
}
|
|
1132
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MosKanbanCell, decorators: [{
|
|
1133
|
+
type: Component,
|
|
1134
|
+
args: [{ selector: 'mos-kanban-cell', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1135
|
+
CdkDropList,
|
|
1136
|
+
CdkDrag,
|
|
1137
|
+
CdkDragHandle,
|
|
1138
|
+
NgTemplateOutlet,
|
|
1139
|
+
MosKanbanCard,
|
|
1140
|
+
MosKanbanCardBlocks,
|
|
1141
|
+
], template: `
|
|
1142
|
+
<div
|
|
1143
|
+
class="mkb-cell-body"
|
|
1144
|
+
cdkDropList
|
|
1145
|
+
[id]="cellId()"
|
|
1146
|
+
[cdkDropListConnectedTo]="connectedTo()"
|
|
1147
|
+
[cdkDropListDisabled]="readOnly()"
|
|
1148
|
+
[cdkDropListEnterPredicate]="enterPredicate"
|
|
1149
|
+
(cdkDropListDropped)="onDrop($event)"
|
|
1150
|
+
role="listbox"
|
|
1151
|
+
aria-multiselectable="true"
|
|
1152
|
+
[attr.aria-label]="label()"
|
|
1153
|
+
[attr.data-column]="columnId()"
|
|
1154
|
+
[attr.data-lane]="laneId() ?? null"
|
|
1155
|
+
>
|
|
1156
|
+
@for (item of items(); track item.id) {
|
|
1157
|
+
<div
|
|
1158
|
+
class="mkb-card"
|
|
1159
|
+
cdkDrag
|
|
1160
|
+
[cdkDragData]="item.id"
|
|
1161
|
+
[cdkDragDisabled]="readOnly()"
|
|
1162
|
+
[cdkDragStartDelay]="dragStartDelay"
|
|
1163
|
+
role="option"
|
|
1164
|
+
tabindex="0"
|
|
1165
|
+
[class.is-selected]="selection().includes(item.id)"
|
|
1166
|
+
[attr.data-item]="item.id"
|
|
1167
|
+
[attr.aria-selected]="selection().includes(item.id)"
|
|
1168
|
+
aria-keyshortcuts="Control+ArrowLeft Control+ArrowRight Control+ArrowUp Control+ArrowDown"
|
|
1169
|
+
(click)="activate(item, $event)"
|
|
1170
|
+
(keydown)="onKeydown(item, $event)"
|
|
1171
|
+
(contextmenu)="onContextMenu(item, $event)"
|
|
1172
|
+
(cdkDragStarted)="lifecycle.emit({ phase: 'start', kind: 'card', id: item.id })"
|
|
1173
|
+
(cdkDragEnded)="lifecycle.emit({ phase: 'end', kind: 'card', id: item.id })"
|
|
1174
|
+
>
|
|
1175
|
+
@if (cardConfig()?.dragHandle && !readOnly()) {
|
|
1176
|
+
<span class="mkb-card-grip" cdkDragHandle aria-hidden="true">⠿</span>
|
|
1177
|
+
}
|
|
1178
|
+
@if (cardTemplate(); as tpl) {
|
|
1179
|
+
<ng-container *ngTemplateOutlet="tpl; context: { $implicit: item }" />
|
|
1180
|
+
} @else if (cardConfig(); as card) {
|
|
1181
|
+
<mos-kanban-card-blocks
|
|
1182
|
+
[blocks]="card.blocks"
|
|
1183
|
+
[item]="item"
|
|
1184
|
+
[registry]="board().registry"
|
|
1185
|
+
[locale]="board().locale"
|
|
1186
|
+
[column]="columnId()"
|
|
1187
|
+
[lane]="laneId()"
|
|
1188
|
+
[allCardIds]="allCardIds()"
|
|
1189
|
+
[canDropEntry]="canDropEntryFn"
|
|
1190
|
+
(action)="cardAction.emit($event)"
|
|
1191
|
+
(entryCheck)="entryCheck.emit($event)"
|
|
1192
|
+
(entryAction)="entryAction.emit($event)"
|
|
1193
|
+
(entryMove)="entryMove.emit($event)"
|
|
1194
|
+
(lifecycle)="lifecycle.emit($event)"
|
|
1195
|
+
/>
|
|
1196
|
+
} @else {
|
|
1197
|
+
<mos-kanban-card [item]="item" />
|
|
1198
|
+
}
|
|
1199
|
+
</div>
|
|
1200
|
+
} @empty {
|
|
1201
|
+
<div class="mkb-cell-empty">Solte itens aqui</div>
|
|
1202
|
+
}
|
|
1203
|
+
</div>
|
|
1204
|
+
`, styles: [":host{display:block;min-width:0}.mkb-cell-body{display:flex;flex-direction:column;gap:var(--mkb-card-gap, 8px);padding:var(--mkb-cell-padding, 4px 8px 10px);overflow-y:auto;min-height:60px;height:100%}.mkb-card{position:relative;display:flex;flex-direction:column;gap:var(--mkb-block-gap, 8px);background:var(--mkb-card-bg, var(--color-panel, #fff));border:1px solid var(--mkb-border, var(--color-border, #e3e0ea));border-radius:var(--mkb-radius-sm, var(--radius-sm, 6px));padding:var(--mkb-card-padding, 10px 12px);cursor:grab;-webkit-tap-highlight-color:transparent;transition:box-shadow var(--mkb-dur, .15s) var(--mkb-ease, ease),border-color var(--mkb-dur, .15s) var(--mkb-ease, ease)}.mkb-card.is-selected{border-color:var(--mkb-accent, #5e35b1);box-shadow:0 0 0 1px var(--mkb-accent, #5e35b1)}.mkb-card:focus-visible{outline:none;box-shadow:var(--mkb-focus-ring, 0 0 0 3px rgba(94, 53, 177, .35))}.mkb-card.cdk-drag-dragging{cursor:grabbing}.mkb-card-grip{position:absolute;top:8px;right:8px;cursor:grab;color:var(--mkb-muted, #6f6a7c);font-size:11px;line-height:1;-webkit-user-select:none;user-select:none}.mkb-card:has(.mkb-card-grip){cursor:default}.mkb-card.cdk-drag-preview{box-shadow:0 10px 28px #00000038;rotate:2deg;opacity:.95}.mkb-card.cdk-drag-animating{transition:transform var(--mkb-dur-move, .18s) var(--mkb-ease, ease)}@media(prefers-reduced-motion:reduce){.mkb-card,.mkb-card.cdk-drag-animating{transition:none}.mkb-card.cdk-drag-preview{rotate:none}}.mkb-card.cdk-drag-placeholder{opacity:.4;border:1px dashed var(--mkb-accent, #5e35b1);background:color-mix(in srgb,var(--mkb-accent, #5e35b1) 6%,var(--mkb-card-bg, #fff))}.mkb-cell-empty{padding:14px 8px;text-align:center;font-size:11.5px;color:var(--mkb-muted, var(--color-muted, #6f6a7c));border:1px dashed var(--mkb-border-strong, #c9c4d4);border-radius:var(--mkb-radius-sm, 6px)}:host-context(.mkb-readonly) .mkb-card{cursor:default}\n"] }]
|
|
1205
|
+
}], propDecorators: { board: [{ type: i0.Input, args: [{ isSignal: true, alias: "board", required: true }] }], columnId: [{ type: i0.Input, args: [{ isSignal: true, alias: "columnId", required: true }] }], laneId: [{ type: i0.Input, args: [{ isSignal: true, alias: "laneId", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], cardTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardTemplate", required: false }] }], cardConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardConfig", required: false }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], connectedTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "connectedTo", required: false }] }], allCardIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "allCardIds", required: false }] }], cardActivate: [{ type: i0.Output, args: ["cardActivate"] }], cardContext: [{ type: i0.Output, args: ["cardContext"] }], cardKey: [{ type: i0.Output, args: ["cardKey"] }], cardAction: [{ type: i0.Output, args: ["cardAction"] }], entryCheck: [{ type: i0.Output, args: ["entryCheck"] }], entryAction: [{ type: i0.Output, args: ["entryAction"] }], entryMove: [{ type: i0.Output, args: ["entryMove"] }], lifecycle: [{ type: i0.Output, args: ["lifecycle"] }], dropRejected: [{ type: i0.Output, args: ["dropRejected"] }] } });
|
|
1206
|
+
|
|
1207
|
+
/** Uma coluna do board SEM lanes: cabeçalho (título/contagem/WIP) + uma única
|
|
1208
|
+
* célula ocupando a coluna inteira. Com lanes, o board monta a grade de
|
|
1209
|
+
* células diretamente. */
|
|
1210
|
+
class MosKanbanColumn {
|
|
1211
|
+
board = input.required(...(ngDevMode ? [{ debugName: "board" }] : /* istanbul ignore next */ []));
|
|
1212
|
+
column = input.required(...(ngDevMode ? [{ debugName: "column" }] : /* istanbul ignore next */ []));
|
|
1213
|
+
items = input.required(...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
1214
|
+
cardTemplate = input(null, ...(ngDevMode ? [{ debugName: "cardTemplate" }] : /* istanbul ignore next */ []));
|
|
1215
|
+
cardConfig = input(null, ...(ngDevMode ? [{ debugName: "cardConfig" }] : /* istanbul ignore next */ []));
|
|
1216
|
+
readOnly = input(false, ...(ngDevMode ? [{ debugName: "readOnly" }] : /* istanbul ignore next */ []));
|
|
1217
|
+
reorderable = input(false, ...(ngDevMode ? [{ debugName: "reorderable" }] : /* istanbul ignore next */ []));
|
|
1218
|
+
selection = input([], ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
|
|
1219
|
+
/** Ids das drop lists de células conectadas (repasse para a célula). */
|
|
1220
|
+
connectedTo = input([], ...(ngDevMode ? [{ debugName: "connectedTo" }] : /* istanbul ignore next */ []));
|
|
1221
|
+
allCardIds = input([], ...(ngDevMode ? [{ debugName: "allCardIds" }] : /* istanbul ignore next */ []));
|
|
1222
|
+
cardActivate = output();
|
|
1223
|
+
cardContext = output();
|
|
1224
|
+
cardKey = output();
|
|
1225
|
+
cardAction = output();
|
|
1226
|
+
entryCheck = output();
|
|
1227
|
+
entryAction = output();
|
|
1228
|
+
entryMove = output();
|
|
1229
|
+
lifecycle = output();
|
|
1230
|
+
dropRejected = output();
|
|
1231
|
+
/** Ctrl+←/→ no cabeçalho: pedido de reordenação (o board executa). */
|
|
1232
|
+
reorderKey = output();
|
|
1233
|
+
isFull = computed(() => {
|
|
1234
|
+
const { count, limit } = this.column();
|
|
1235
|
+
return limit !== undefined && count >= limit;
|
|
1236
|
+
}, ...(ngDevMode ? [{ debugName: "isFull" }] : /* istanbul ignore next */ []));
|
|
1237
|
+
/** Cor do cabeçalho vinda de group.meta.color (a lib não interpreta meta
|
|
1238
|
+
* além disto). */
|
|
1239
|
+
color = computed(() => {
|
|
1240
|
+
const c = this.column().meta?.['color'];
|
|
1241
|
+
return typeof c === 'string' ? c : null;
|
|
1242
|
+
}, ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
1243
|
+
onHeaderKeydown(event) {
|
|
1244
|
+
if (!this.reorderable() || !(event.ctrlKey || event.metaKey))
|
|
1245
|
+
return;
|
|
1246
|
+
if (event.key === 'ArrowLeft') {
|
|
1247
|
+
event.preventDefault();
|
|
1248
|
+
this.reorderKey.emit('left');
|
|
1249
|
+
}
|
|
1250
|
+
else if (event.key === 'ArrowRight') {
|
|
1251
|
+
event.preventDefault();
|
|
1252
|
+
this.reorderKey.emit('right');
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MosKanbanColumn, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1256
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.18", type: MosKanbanColumn, isStandalone: true, selector: "mos-kanban-column", inputs: { board: { classPropertyName: "board", publicName: "board", isSignal: true, isRequired: true, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, cardTemplate: { classPropertyName: "cardTemplate", publicName: "cardTemplate", isSignal: true, isRequired: false, transformFunction: null }, cardConfig: { classPropertyName: "cardConfig", publicName: "cardConfig", isSignal: true, isRequired: false, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, reorderable: { classPropertyName: "reorderable", publicName: "reorderable", isSignal: true, isRequired: false, transformFunction: null }, selection: { classPropertyName: "selection", publicName: "selection", isSignal: true, isRequired: false, transformFunction: null }, connectedTo: { classPropertyName: "connectedTo", publicName: "connectedTo", isSignal: true, isRequired: false, transformFunction: null }, allCardIds: { classPropertyName: "allCardIds", publicName: "allCardIds", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { cardActivate: "cardActivate", cardContext: "cardContext", cardKey: "cardKey", cardAction: "cardAction", entryCheck: "entryCheck", entryAction: "entryAction", entryMove: "entryMove", lifecycle: "lifecycle", dropRejected: "dropRejected", reorderKey: "reorderKey" }, host: { properties: { "style.--mkb-col-accent": "color()" } }, ngImport: i0, template: `
|
|
1257
|
+
<header
|
|
1258
|
+
class="mkb-col-header"
|
|
1259
|
+
[class.mkb-col-accented]="!!color()"
|
|
1260
|
+
[class.mkb-col-reorderable]="reorderable()"
|
|
1261
|
+
[attr.tabindex]="reorderable() ? 0 : null"
|
|
1262
|
+
[attr.data-column-header]="column().id"
|
|
1263
|
+
[attr.role]="reorderable() ? 'button' : null"
|
|
1264
|
+
[attr.aria-label]="reorderable() ? 'Coluna ' + (column().title ?? column().id) + ' — Ctrl+setas para reordenar' : null"
|
|
1265
|
+
[attr.aria-keyshortcuts]="reorderable() ? 'Control+ArrowLeft Control+ArrowRight' : null"
|
|
1266
|
+
(keydown)="onHeaderKeydown($event)"
|
|
1267
|
+
>
|
|
1268
|
+
<!-- alça CDK projetada pelo board: o cdkDragHandle precisa ser
|
|
1269
|
+
conteúdo do MESMO cdkDrag (aplicado no host deste componente) -->
|
|
1270
|
+
<ng-content select="[mkbColHandle]" />
|
|
1271
|
+
<span class="mkb-col-title">{{ column().title ?? column().id }}</span>
|
|
1272
|
+
<span
|
|
1273
|
+
class="mkb-col-count"
|
|
1274
|
+
[class.is-full]="isFull()"
|
|
1275
|
+
[attr.title]="isFull() ? 'Limite de itens atingido' : null"
|
|
1276
|
+
>
|
|
1277
|
+
{{ column().count }}{{ column().limit !== undefined ? ' / ' + column().limit : '' }}
|
|
1278
|
+
</span>
|
|
1279
|
+
</header>
|
|
1280
|
+
|
|
1281
|
+
<mos-kanban-cell
|
|
1282
|
+
class="mkb-col-cell"
|
|
1283
|
+
[board]="board()"
|
|
1284
|
+
[columnId]="column().id"
|
|
1285
|
+
[items]="items()"
|
|
1286
|
+
[cardTemplate]="cardTemplate()"
|
|
1287
|
+
[cardConfig]="cardConfig()"
|
|
1288
|
+
[readOnly]="readOnly()"
|
|
1289
|
+
[selection]="selection()"
|
|
1290
|
+
[connectedTo]="connectedTo()"
|
|
1291
|
+
[allCardIds]="allCardIds()"
|
|
1292
|
+
(cardActivate)="cardActivate.emit($event)"
|
|
1293
|
+
(cardContext)="cardContext.emit($event)"
|
|
1294
|
+
(cardKey)="cardKey.emit($event)"
|
|
1295
|
+
(cardAction)="cardAction.emit($event)"
|
|
1296
|
+
(entryCheck)="entryCheck.emit($event)"
|
|
1297
|
+
(entryAction)="entryAction.emit($event)"
|
|
1298
|
+
(entryMove)="entryMove.emit($event)"
|
|
1299
|
+
(lifecycle)="lifecycle.emit($event)"
|
|
1300
|
+
(dropRejected)="dropRejected.emit($event)"
|
|
1301
|
+
/>
|
|
1302
|
+
`, isInline: true, styles: [":host{display:flex;flex-direction:column;min-width:0;max-height:100%;background:var(--mkb-column-bg, var(--color-surface, #efedf3));border:1px solid var(--mkb-border, var(--color-border, #e3e0ea));border-radius:var(--mkb-radius, var(--radius-md, 10px))}.mkb-col-header{display:flex;align-items:center;gap:8px;padding:var(--mkb-header-padding, 10px 12px)}.mkb-col-header .mkb-col-title{flex:1}.mkb-col-header:focus-visible{outline:none;box-shadow:var(--mkb-focus-ring, 0 0 0 3px rgba(94, 53, 177, .35));border-radius:var(--mkb-radius-sm, 6px)}.mkb-col-grip{cursor:grab;color:var(--mkb-muted, #6f6a7c);font-size:12px;line-height:1;-webkit-user-select:none;user-select:none}.mkb-col-grip:active{cursor:grabbing}.mkb-col-header.mkb-col-accented{background:color-mix(in srgb,var(--mkb-col-accent) 16%,transparent);border-top:3px solid var(--mkb-col-accent);border-radius:var(--mkb-radius, 10px) var(--mkb-radius, 10px) 0 0}.mkb-col-title{font-size:12.5px;font-weight:600;letter-spacing:.2px;color:var(--mkb-foreground, var(--color-foreground, #241f33))}.mkb-col-count{font-size:11px;font-weight:500;padding:1px 8px;border-radius:999px;background:var(--mkb-card-bg, var(--color-panel, #fff));color:var(--mkb-muted, var(--color-muted, #6f6a7c))}.mkb-col-count.is-full{color:var(--mkb-danger, #b3261e)}.mkb-col-cell{flex:1;min-height:0}\n"], dependencies: [{ kind: "component", type: MosKanbanCell, selector: "mos-kanban-cell", inputs: ["board", "columnId", "laneId", "items", "cardTemplate", "cardConfig", "readOnly", "selection", "connectedTo", "allCardIds"], outputs: ["cardActivate", "cardContext", "cardKey", "cardAction", "entryCheck", "entryAction", "entryMove", "lifecycle", "dropRejected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1303
|
+
}
|
|
1304
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MosKanbanColumn, decorators: [{
|
|
1305
|
+
type: Component,
|
|
1306
|
+
args: [{ selector: 'mos-kanban-column', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MosKanbanCell], template: `
|
|
1307
|
+
<header
|
|
1308
|
+
class="mkb-col-header"
|
|
1309
|
+
[class.mkb-col-accented]="!!color()"
|
|
1310
|
+
[class.mkb-col-reorderable]="reorderable()"
|
|
1311
|
+
[attr.tabindex]="reorderable() ? 0 : null"
|
|
1312
|
+
[attr.data-column-header]="column().id"
|
|
1313
|
+
[attr.role]="reorderable() ? 'button' : null"
|
|
1314
|
+
[attr.aria-label]="reorderable() ? 'Coluna ' + (column().title ?? column().id) + ' — Ctrl+setas para reordenar' : null"
|
|
1315
|
+
[attr.aria-keyshortcuts]="reorderable() ? 'Control+ArrowLeft Control+ArrowRight' : null"
|
|
1316
|
+
(keydown)="onHeaderKeydown($event)"
|
|
1317
|
+
>
|
|
1318
|
+
<!-- alça CDK projetada pelo board: o cdkDragHandle precisa ser
|
|
1319
|
+
conteúdo do MESMO cdkDrag (aplicado no host deste componente) -->
|
|
1320
|
+
<ng-content select="[mkbColHandle]" />
|
|
1321
|
+
<span class="mkb-col-title">{{ column().title ?? column().id }}</span>
|
|
1322
|
+
<span
|
|
1323
|
+
class="mkb-col-count"
|
|
1324
|
+
[class.is-full]="isFull()"
|
|
1325
|
+
[attr.title]="isFull() ? 'Limite de itens atingido' : null"
|
|
1326
|
+
>
|
|
1327
|
+
{{ column().count }}{{ column().limit !== undefined ? ' / ' + column().limit : '' }}
|
|
1328
|
+
</span>
|
|
1329
|
+
</header>
|
|
1330
|
+
|
|
1331
|
+
<mos-kanban-cell
|
|
1332
|
+
class="mkb-col-cell"
|
|
1333
|
+
[board]="board()"
|
|
1334
|
+
[columnId]="column().id"
|
|
1335
|
+
[items]="items()"
|
|
1336
|
+
[cardTemplate]="cardTemplate()"
|
|
1337
|
+
[cardConfig]="cardConfig()"
|
|
1338
|
+
[readOnly]="readOnly()"
|
|
1339
|
+
[selection]="selection()"
|
|
1340
|
+
[connectedTo]="connectedTo()"
|
|
1341
|
+
[allCardIds]="allCardIds()"
|
|
1342
|
+
(cardActivate)="cardActivate.emit($event)"
|
|
1343
|
+
(cardContext)="cardContext.emit($event)"
|
|
1344
|
+
(cardKey)="cardKey.emit($event)"
|
|
1345
|
+
(cardAction)="cardAction.emit($event)"
|
|
1346
|
+
(entryCheck)="entryCheck.emit($event)"
|
|
1347
|
+
(entryAction)="entryAction.emit($event)"
|
|
1348
|
+
(entryMove)="entryMove.emit($event)"
|
|
1349
|
+
(lifecycle)="lifecycle.emit($event)"
|
|
1350
|
+
(dropRejected)="dropRejected.emit($event)"
|
|
1351
|
+
/>
|
|
1352
|
+
`, host: { '[style.--mkb-col-accent]': 'color()' }, styles: [":host{display:flex;flex-direction:column;min-width:0;max-height:100%;background:var(--mkb-column-bg, var(--color-surface, #efedf3));border:1px solid var(--mkb-border, var(--color-border, #e3e0ea));border-radius:var(--mkb-radius, var(--radius-md, 10px))}.mkb-col-header{display:flex;align-items:center;gap:8px;padding:var(--mkb-header-padding, 10px 12px)}.mkb-col-header .mkb-col-title{flex:1}.mkb-col-header:focus-visible{outline:none;box-shadow:var(--mkb-focus-ring, 0 0 0 3px rgba(94, 53, 177, .35));border-radius:var(--mkb-radius-sm, 6px)}.mkb-col-grip{cursor:grab;color:var(--mkb-muted, #6f6a7c);font-size:12px;line-height:1;-webkit-user-select:none;user-select:none}.mkb-col-grip:active{cursor:grabbing}.mkb-col-header.mkb-col-accented{background:color-mix(in srgb,var(--mkb-col-accent) 16%,transparent);border-top:3px solid var(--mkb-col-accent);border-radius:var(--mkb-radius, 10px) var(--mkb-radius, 10px) 0 0}.mkb-col-title{font-size:12.5px;font-weight:600;letter-spacing:.2px;color:var(--mkb-foreground, var(--color-foreground, #241f33))}.mkb-col-count{font-size:11px;font-weight:500;padding:1px 8px;border-radius:999px;background:var(--mkb-card-bg, var(--color-panel, #fff));color:var(--mkb-muted, var(--color-muted, #6f6a7c))}.mkb-col-count.is-full{color:var(--mkb-danger, #b3261e)}.mkb-col-cell{flex:1;min-height:0}\n"] }]
|
|
1353
|
+
}], propDecorators: { board: [{ type: i0.Input, args: [{ isSignal: true, alias: "board", required: true }] }], column: [{ type: i0.Input, args: [{ isSignal: true, alias: "column", required: true }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], cardTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardTemplate", required: false }] }], cardConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "cardConfig", required: false }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], reorderable: [{ type: i0.Input, args: [{ isSignal: true, alias: "reorderable", required: false }] }], selection: [{ type: i0.Input, args: [{ isSignal: true, alias: "selection", required: false }] }], connectedTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "connectedTo", required: false }] }], allCardIds: [{ type: i0.Input, args: [{ isSignal: true, alias: "allCardIds", required: false }] }], cardActivate: [{ type: i0.Output, args: ["cardActivate"] }], cardContext: [{ type: i0.Output, args: ["cardContext"] }], cardKey: [{ type: i0.Output, args: ["cardKey"] }], cardAction: [{ type: i0.Output, args: ["cardAction"] }], entryCheck: [{ type: i0.Output, args: ["entryCheck"] }], entryAction: [{ type: i0.Output, args: ["entryAction"] }], entryMove: [{ type: i0.Output, args: ["entryMove"] }], lifecycle: [{ type: i0.Output, args: ["lifecycle"] }], dropRejected: [{ type: i0.Output, args: ["dropRejected"] }], reorderKey: [{ type: i0.Output, args: ["reorderKey"] }] } });
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* <mos-kanban-board> — raiz da camada visual.
|
|
1357
|
+
*
|
|
1358
|
+
* Recebe um KanbanBoard do core e o REFLETE: nenhuma regra vive aqui. Dois
|
|
1359
|
+
* layouts: colunas (sem lanes) e grade coluna × lane (swimlanes) — nos dois,
|
|
1360
|
+
* a unidade de drop é a CÉLULA. A ponte de reatividade é um signal de versão
|
|
1361
|
+
* incrementado a cada `change` do board (zoneless-friendly, OnPush).
|
|
1362
|
+
*/
|
|
1363
|
+
class MosKanbanBoard {
|
|
1364
|
+
/** Instância criada com createBoard() do core. */
|
|
1365
|
+
board = input(null, ...(ngDevMode ? [{ debugName: "board" }] : /* istanbul ignore next */ []));
|
|
1366
|
+
loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
|
|
1367
|
+
error = input(null, ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
1368
|
+
/** Caixa de pesquisa embutida (gerencia o filtro de texto do board,
|
|
1369
|
+
* preservando os demais filtros do host). */
|
|
1370
|
+
showSearch = input(false, ...(ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []));
|
|
1371
|
+
/** Ações do menu contextual dos cartões; vazio = sem menu. */
|
|
1372
|
+
itemActions = input([], ...(ngDevMode ? [{ debugName: "itemActions" }] : /* istanbul ignore next */ []));
|
|
1373
|
+
itemClick = output();
|
|
1374
|
+
/** Espelha o after:move do core (qualquer origem: drag, comando, undo). */
|
|
1375
|
+
itemMove = output();
|
|
1376
|
+
/** Drop recusado pelas regras — gancho para toast/feedback do host. */
|
|
1377
|
+
moveRejected = output();
|
|
1378
|
+
itemAction = output();
|
|
1379
|
+
/** Ação disparada numa ENTRY (bloco items, capability 'action'). */
|
|
1380
|
+
entryAction = output();
|
|
1381
|
+
/** Ciclo do gesto (qualquer nível): início / fim / cancelado por Esc.
|
|
1382
|
+
* `dragCanceled` é emitido ADICIONALMENTE ao `dragEnded` (docs/02 §9). */
|
|
1383
|
+
dragStarted = output();
|
|
1384
|
+
dragEnded = output();
|
|
1385
|
+
dragCanceled = output();
|
|
1386
|
+
version = MKB_VERSION;
|
|
1387
|
+
cardDef = contentChild(MosKanbanCardDef, ...(ngDevMode ? [{ debugName: "cardDef" }] : /* istanbul ignore next */ []));
|
|
1388
|
+
menu = signal(null, ...(ngDevMode ? [{ debugName: "menu" }] : /* istanbul ignore next */ []));
|
|
1389
|
+
/** Mensagem do aria-live (movimentos por teclado e recusas). */
|
|
1390
|
+
announcement = signal('', ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
1391
|
+
elementRef = inject(ElementRef);
|
|
1392
|
+
/** Flag de cancelamento por Esc (compartilhado com as células via DI). */
|
|
1393
|
+
cancelFlag = inject(DragCancelFlag);
|
|
1394
|
+
/** Incrementado a cada mudança do board — invalida os computed. */
|
|
1395
|
+
revision = signal(0, ...(ngDevMode ? [{ debugName: "revision" }] : /* istanbul ignore next */ []));
|
|
1396
|
+
constructor() {
|
|
1397
|
+
effect((onCleanup) => {
|
|
1398
|
+
const board = this.board();
|
|
1399
|
+
if (!board)
|
|
1400
|
+
return;
|
|
1401
|
+
const offChange = board.subscribe(() => this.revision.update((r) => r + 1));
|
|
1402
|
+
const offMove = board.on('after:move', (ev) => this.itemMove.emit(ev));
|
|
1403
|
+
onCleanup(() => {
|
|
1404
|
+
offChange();
|
|
1405
|
+
offMove();
|
|
1406
|
+
});
|
|
1407
|
+
});
|
|
1408
|
+
// GUARDA DE INTERATIVOS (spike D0/E4): um único listener em CAPTURE na
|
|
1409
|
+
// raiz do board impede que mousedown/touchstart originados em elementos
|
|
1410
|
+
// interativos alcancem qualquer cdkDrag — nenhum arraste (de nível algum)
|
|
1411
|
+
// inicia a partir de botão/link/input; o click deles segue normal.
|
|
1412
|
+
const guard = (event) => {
|
|
1413
|
+
const target = event.target;
|
|
1414
|
+
if (target?.closest?.('button, a[href], input, select, textarea, [contenteditable="true"], [data-mkb-no-drag]')) {
|
|
1415
|
+
event.stopPropagation();
|
|
1416
|
+
}
|
|
1417
|
+
};
|
|
1418
|
+
this.elementRef.nativeElement.addEventListener('mousedown', guard, true);
|
|
1419
|
+
this.elementRef.nativeElement.addEventListener('touchstart', guard, true);
|
|
1420
|
+
}
|
|
1421
|
+
readOnly = computed(() => {
|
|
1422
|
+
this.revision();
|
|
1423
|
+
return this.board()?.state.readOnly ?? false;
|
|
1424
|
+
}, ...(ngDevMode ? [{ debugName: "readOnly" }] : /* istanbul ignore next */ []));
|
|
1425
|
+
selection = computed(() => {
|
|
1426
|
+
this.revision();
|
|
1427
|
+
return this.board()?.state.selection ?? [];
|
|
1428
|
+
}, ...(ngDevMode ? [{ debugName: "selection" }] : /* istanbul ignore next */ []));
|
|
1429
|
+
columnVMs = computed(() => {
|
|
1430
|
+
this.revision();
|
|
1431
|
+
const board = this.board();
|
|
1432
|
+
if (!board)
|
|
1433
|
+
return [];
|
|
1434
|
+
return board.columns().map((column) => ({
|
|
1435
|
+
column,
|
|
1436
|
+
items: board.visibleItemsIn(column.id),
|
|
1437
|
+
}));
|
|
1438
|
+
}, ...(ngDevMode ? [{ debugName: "columnVMs" }] : /* istanbul ignore next */ []));
|
|
1439
|
+
/** null = sem eixo de lanes (layout de colunas). */
|
|
1440
|
+
laneVMs = computed(() => {
|
|
1441
|
+
this.revision();
|
|
1442
|
+
const board = this.board();
|
|
1443
|
+
const lanes = board?.lanes();
|
|
1444
|
+
if (!board || !lanes)
|
|
1445
|
+
return null;
|
|
1446
|
+
return lanes.map((lane) => ({
|
|
1447
|
+
lane,
|
|
1448
|
+
cells: board.columns().map((column) => ({
|
|
1449
|
+
column,
|
|
1450
|
+
items: board.visibleItemsIn(column.id, lane.id),
|
|
1451
|
+
})),
|
|
1452
|
+
}));
|
|
1453
|
+
}, ...(ngDevMode ? [{ debugName: "laneVMs" }] : /* istanbul ignore next */ []));
|
|
1454
|
+
/** Modelo declarativo do cartão (da config do board); null = cartão default. */
|
|
1455
|
+
cardConfig = computed(() => this.board()?.cardConfig ?? null, ...(ngDevMode ? [{ debugName: "cardConfig" }] : /* istanbul ignore next */ []));
|
|
1456
|
+
/** Ids de todos os cards (conexões do move-across de entries). */
|
|
1457
|
+
allCardIds = computed(() => {
|
|
1458
|
+
this.revision();
|
|
1459
|
+
return this.board()?.state.items.map((i) => i.id) ?? [];
|
|
1460
|
+
}, ...(ngDevMode ? [{ debugName: "allCardIds" }] : /* istanbul ignore next */ []));
|
|
1461
|
+
columnsReorderable = computed(() => {
|
|
1462
|
+
this.revision();
|
|
1463
|
+
return this.board()?.isReorderable('columns') ?? false;
|
|
1464
|
+
}, ...(ngDevMode ? [{ debugName: "columnsReorderable" }] : /* istanbul ignore next */ []));
|
|
1465
|
+
lanesReorderable = computed(() => {
|
|
1466
|
+
this.revision();
|
|
1467
|
+
return this.board()?.isReorderable('lanes') ?? false;
|
|
1468
|
+
}, ...(ngDevMode ? [{ debugName: "lanesReorderable" }] : /* istanbul ignore next */ []));
|
|
1469
|
+
isFull(group) {
|
|
1470
|
+
return group.limit !== undefined && group.count >= group.limit;
|
|
1471
|
+
}
|
|
1472
|
+
/** Cor de group.meta.color (cabeçalhos de coluna da grade com swimlanes). */
|
|
1473
|
+
colColor(group) {
|
|
1474
|
+
const c = group.meta?.['color'];
|
|
1475
|
+
return typeof c === 'string' ? c : null;
|
|
1476
|
+
}
|
|
1477
|
+
// ---- Reordenação de colunas/lanes (spike D0: CDK com listas ISOLADAS) -----
|
|
1478
|
+
// Cada nível tem sua drop list AUTÔNOMA (sem cdkDropListGroup): a lista de
|
|
1479
|
+
// colunas e a de lanes não se conectam a nada; as células conectam-se só
|
|
1480
|
+
// entre si via cdkDropListConnectedTo explícito. Colisão só entre iguais.
|
|
1481
|
+
/** Ids das drop lists de TODAS as células (para o connectedTo). */
|
|
1482
|
+
cellIds = computed(() => {
|
|
1483
|
+
const lanes = this.laneVMs();
|
|
1484
|
+
const cols = this.columnVMs();
|
|
1485
|
+
if (lanes) {
|
|
1486
|
+
const ids = [];
|
|
1487
|
+
for (const laneVM of lanes) {
|
|
1488
|
+
for (const vm of cols)
|
|
1489
|
+
ids.push(cellListId(vm.column.id, laneVM.lane.id));
|
|
1490
|
+
}
|
|
1491
|
+
return ids;
|
|
1492
|
+
}
|
|
1493
|
+
return cols.map((vm) => cellListId(vm.column.id));
|
|
1494
|
+
}, ...(ngDevMode ? [{ debugName: "cellIds" }] : /* istanbul ignore next */ []));
|
|
1495
|
+
/** Conexões de uma célula = todas as demais células (nunca listas de grupo). */
|
|
1496
|
+
connectedFor(columnId, laneId) {
|
|
1497
|
+
const self = cellListId(columnId, laneId);
|
|
1498
|
+
return this.cellIds().filter((id) => id !== self);
|
|
1499
|
+
}
|
|
1500
|
+
onColumnDrop(event) {
|
|
1501
|
+
if (this.cancelFlag.consume())
|
|
1502
|
+
return;
|
|
1503
|
+
if (event.previousIndex === event.currentIndex)
|
|
1504
|
+
return;
|
|
1505
|
+
const id = event.item.data.id;
|
|
1506
|
+
this.moveGroup('columns', id, event.currentIndex);
|
|
1507
|
+
}
|
|
1508
|
+
onLaneDrop(event) {
|
|
1509
|
+
if (this.cancelFlag.consume())
|
|
1510
|
+
return;
|
|
1511
|
+
if (event.previousIndex === event.currentIndex)
|
|
1512
|
+
return;
|
|
1513
|
+
const id = event.item.data.id;
|
|
1514
|
+
this.moveGroup('lanes', id, event.currentIndex);
|
|
1515
|
+
}
|
|
1516
|
+
/** Linhas-fantasma do preview de fatia de coluna (grade de swimlanes). */
|
|
1517
|
+
ghostRows(count) {
|
|
1518
|
+
return Array.from({ length: Math.max(1, Math.min(count, 4)) });
|
|
1519
|
+
}
|
|
1520
|
+
/** Traduz o ciclo dos cdkDrag* nos outputs públicos e memoriza o gesto
|
|
1521
|
+
* ativo (para o dragCanceled do Esc). */
|
|
1522
|
+
lastDrag = null;
|
|
1523
|
+
onLifecycle(ev) {
|
|
1524
|
+
const info = { kind: ev.kind, id: ev.id };
|
|
1525
|
+
if (ev.phase === 'start') {
|
|
1526
|
+
this.lastDrag = info;
|
|
1527
|
+
this.dragStarted.emit(info);
|
|
1528
|
+
}
|
|
1529
|
+
else {
|
|
1530
|
+
this.lastDrag = null;
|
|
1531
|
+
this.dragEnded.emit(info);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
/** Check de entry (bloco items): vira comando updateEntry no core. */
|
|
1535
|
+
onEntryCheck(ev) {
|
|
1536
|
+
const result = this.board()?.execute({
|
|
1537
|
+
type: 'updateEntry',
|
|
1538
|
+
cardId: ev.cardId,
|
|
1539
|
+
field: ev.field,
|
|
1540
|
+
entryId: ev.entryId,
|
|
1541
|
+
changes: { [ev.checkField]: ev.value },
|
|
1542
|
+
});
|
|
1543
|
+
if (result && !result.ok)
|
|
1544
|
+
this.announce(`Alteração recusada: ${result.reason}`);
|
|
1545
|
+
}
|
|
1546
|
+
/** Movimentação de entry (bloco items): vira comando moveEntry no core —
|
|
1547
|
+
* dentro do card ou entre cards (move-across). */
|
|
1548
|
+
onEntryMove(ev) {
|
|
1549
|
+
const result = this.board()?.execute({
|
|
1550
|
+
type: 'moveEntry',
|
|
1551
|
+
cardId: ev.cardId,
|
|
1552
|
+
field: ev.field,
|
|
1553
|
+
entryId: ev.entryId,
|
|
1554
|
+
index: ev.index,
|
|
1555
|
+
toCardId: ev.toCardId,
|
|
1556
|
+
});
|
|
1557
|
+
if (result && !result.ok)
|
|
1558
|
+
this.announce(`Movimento recusado: ${result.reason}`);
|
|
1559
|
+
}
|
|
1560
|
+
/** Esc durante um drag: arma o flag e encerra o gesto com mouseup sintético
|
|
1561
|
+
* — o drop dispara, os handlers consomem o flag e nenhum comando executa. */
|
|
1562
|
+
onEscape() {
|
|
1563
|
+
if (this.menu()) {
|
|
1564
|
+
this.closeMenu();
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
if (document.querySelector('.cdk-drop-list-dragging')) {
|
|
1568
|
+
this.cancelFlag.arm();
|
|
1569
|
+
if (this.lastDrag)
|
|
1570
|
+
this.dragCanceled.emit(this.lastDrag);
|
|
1571
|
+
document.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
|
|
1572
|
+
this.announce('Movimento cancelado');
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
/** Ctrl+←/→ na coluna (layout de colunas, via componente). */
|
|
1576
|
+
onColumnReorderKey(id, dir) {
|
|
1577
|
+
this.nudgeGroup('columns', id, dir === 'left' ? -1 : 1);
|
|
1578
|
+
}
|
|
1579
|
+
onColumnHeaderKeydown(id, event) {
|
|
1580
|
+
if (!this.columnsReorderable() || !(event.ctrlKey || event.metaKey))
|
|
1581
|
+
return;
|
|
1582
|
+
if (event.key === 'ArrowLeft') {
|
|
1583
|
+
event.preventDefault();
|
|
1584
|
+
this.nudgeGroup('columns', id, -1);
|
|
1585
|
+
}
|
|
1586
|
+
else if (event.key === 'ArrowRight') {
|
|
1587
|
+
event.preventDefault();
|
|
1588
|
+
this.nudgeGroup('columns', id, 1);
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
onLaneHeaderKeydown(id, event) {
|
|
1592
|
+
if (!this.lanesReorderable() || !(event.ctrlKey || event.metaKey))
|
|
1593
|
+
return;
|
|
1594
|
+
if (event.key === 'ArrowUp') {
|
|
1595
|
+
event.preventDefault();
|
|
1596
|
+
this.nudgeGroup('lanes', id, -1);
|
|
1597
|
+
}
|
|
1598
|
+
else if (event.key === 'ArrowDown') {
|
|
1599
|
+
event.preventDefault();
|
|
1600
|
+
this.nudgeGroup('lanes', id, 1);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
nudgeGroup(axis, id, delta) {
|
|
1604
|
+
const board = this.board();
|
|
1605
|
+
if (!board)
|
|
1606
|
+
return;
|
|
1607
|
+
const order = axis === 'columns' ? board.state.columnOrder : board.state.laneOrder;
|
|
1608
|
+
const from = order.indexOf(id);
|
|
1609
|
+
if (from === -1)
|
|
1610
|
+
return;
|
|
1611
|
+
this.moveGroup(axis, id, from + delta, true);
|
|
1612
|
+
}
|
|
1613
|
+
moveGroup(axis, groupId, index, refocusHeader = false) {
|
|
1614
|
+
const board = this.board();
|
|
1615
|
+
if (!board)
|
|
1616
|
+
return;
|
|
1617
|
+
const result = board.execute({ type: 'moveGroup', axis, groupId, index });
|
|
1618
|
+
const label = axis === 'columns' ? 'Coluna' : 'Lane';
|
|
1619
|
+
if (result.ok) {
|
|
1620
|
+
this.announce(`${label} ${groupId} reposicionada`);
|
|
1621
|
+
if (refocusHeader)
|
|
1622
|
+
this.focusHeaderSoon(axis, groupId);
|
|
1623
|
+
}
|
|
1624
|
+
else {
|
|
1625
|
+
this.announce(`Reordenação recusada: ${result.reason}`);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
focusHeaderSoon(axis, groupId) {
|
|
1629
|
+
const attr = axis === 'columns' ? 'data-column-header' : 'data-lane-header';
|
|
1630
|
+
setTimeout(() => {
|
|
1631
|
+
const nodes = this.elementRef.nativeElement.querySelectorAll(`[${attr}]`);
|
|
1632
|
+
for (const el of Array.from(nodes)) {
|
|
1633
|
+
if (el.getAttribute(attr) === groupId) {
|
|
1634
|
+
el.focus();
|
|
1635
|
+
return;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
onActivate({ item, toggle }) {
|
|
1641
|
+
const board = this.board();
|
|
1642
|
+
if (board) {
|
|
1643
|
+
const current = board.state.selection;
|
|
1644
|
+
const itemIds = toggle
|
|
1645
|
+
? current.includes(item.id)
|
|
1646
|
+
? current.filter((id) => id !== item.id)
|
|
1647
|
+
: [...current, item.id]
|
|
1648
|
+
: [item.id];
|
|
1649
|
+
board.execute({ type: 'setSelection', itemIds });
|
|
1650
|
+
}
|
|
1651
|
+
this.itemClick.emit(item);
|
|
1652
|
+
}
|
|
1653
|
+
onCardContext(request) {
|
|
1654
|
+
if (this.itemActions().length === 0)
|
|
1655
|
+
return;
|
|
1656
|
+
const board = this.board();
|
|
1657
|
+
if (board && !board.state.selection.includes(request.item.id)) {
|
|
1658
|
+
board.execute({ type: 'setSelection', itemIds: [request.item.id] });
|
|
1659
|
+
}
|
|
1660
|
+
this.menu.set({ item: request.item, x: request.x, y: request.y });
|
|
1661
|
+
// foco no primeiro item do menu depois de renderizar
|
|
1662
|
+
setTimeout(() => this.elementRef.nativeElement
|
|
1663
|
+
.querySelector('.mkb-menu button:not(:disabled)')
|
|
1664
|
+
?.focus());
|
|
1665
|
+
}
|
|
1666
|
+
invoke(item, actionId) {
|
|
1667
|
+
this.itemAction.emit({ item, actionId });
|
|
1668
|
+
this.closeMenu();
|
|
1669
|
+
}
|
|
1670
|
+
closeMenu() {
|
|
1671
|
+
const open = this.menu();
|
|
1672
|
+
if (!open)
|
|
1673
|
+
return;
|
|
1674
|
+
this.menu.set(null);
|
|
1675
|
+
this.focusCardSoon(open.item.id); // devolve o foco ao cartão
|
|
1676
|
+
}
|
|
1677
|
+
// ---- Teclado (DnD acessível) ---------------------------------------------
|
|
1678
|
+
/** Setas navegam o foco entre cartões; Ctrl+setas MOVEM o cartão
|
|
1679
|
+
* (Ctrl+Shift+↑/↓ move entre lanes). Mesmo caminho de comando do mouse. */
|
|
1680
|
+
onCardKey(ev) {
|
|
1681
|
+
const board = this.board();
|
|
1682
|
+
const loc = this.locate(ev.item.id);
|
|
1683
|
+
if (!board || !loc)
|
|
1684
|
+
return;
|
|
1685
|
+
if (ev.ctrl)
|
|
1686
|
+
this.moveByKey(board, ev, loc);
|
|
1687
|
+
else
|
|
1688
|
+
this.focusByKey(ev, loc);
|
|
1689
|
+
}
|
|
1690
|
+
onDropRejected(rejection) {
|
|
1691
|
+
this.announce(`Movimento recusado: ${rejection.reason}`);
|
|
1692
|
+
this.moveRejected.emit(rejection);
|
|
1693
|
+
}
|
|
1694
|
+
/** Localiza o cartão nas VMs atuais (índices de coluna/lane + posição
|
|
1695
|
+
* visível na célula). */
|
|
1696
|
+
locate(itemId) {
|
|
1697
|
+
const lanes = this.laneVMs();
|
|
1698
|
+
if (lanes) {
|
|
1699
|
+
for (let l = 0; l < lanes.length; l++) {
|
|
1700
|
+
const cells = lanes[l].cells;
|
|
1701
|
+
for (let c = 0; c < cells.length; c++) {
|
|
1702
|
+
const i = cells[c].items.findIndex((it) => it.id === itemId);
|
|
1703
|
+
if (i !== -1)
|
|
1704
|
+
return { colIdx: c, laneIdx: l, visIdx: i };
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
return null;
|
|
1708
|
+
}
|
|
1709
|
+
const cols = this.columnVMs();
|
|
1710
|
+
for (let c = 0; c < cols.length; c++) {
|
|
1711
|
+
const i = cols[c].items.findIndex((it) => it.id === itemId);
|
|
1712
|
+
if (i !== -1)
|
|
1713
|
+
return { colIdx: c, laneIdx: -1, visIdx: i };
|
|
1714
|
+
}
|
|
1715
|
+
return null;
|
|
1716
|
+
}
|
|
1717
|
+
cellItems(colIdx, laneIdx) {
|
|
1718
|
+
const lanes = this.laneVMs();
|
|
1719
|
+
return ((lanes ? lanes[laneIdx]?.cells[colIdx] : this.columnVMs()[colIdx])?.items ?? []);
|
|
1720
|
+
}
|
|
1721
|
+
focusByKey(ev, loc) {
|
|
1722
|
+
let target;
|
|
1723
|
+
if (ev.key === 'ArrowUp' || ev.key === 'ArrowDown') {
|
|
1724
|
+
const delta = ev.key === 'ArrowDown' ? 1 : -1;
|
|
1725
|
+
target = this.cellItems(loc.colIdx, loc.laneIdx)[loc.visIdx + delta];
|
|
1726
|
+
}
|
|
1727
|
+
else {
|
|
1728
|
+
const dir = ev.key === 'ArrowRight' ? 1 : -1;
|
|
1729
|
+
const count = this.columnVMs().length;
|
|
1730
|
+
for (let c = loc.colIdx + dir; c >= 0 && c < count; c += dir) {
|
|
1731
|
+
const items = this.cellItems(c, loc.laneIdx);
|
|
1732
|
+
if (items.length) {
|
|
1733
|
+
target = items[Math.min(loc.visIdx, items.length - 1)];
|
|
1734
|
+
break;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
if (target)
|
|
1739
|
+
this.focusCard(target.id);
|
|
1740
|
+
}
|
|
1741
|
+
moveByKey(board, ev, loc) {
|
|
1742
|
+
const lanes = this.laneVMs();
|
|
1743
|
+
const column = this.columnVMs()[loc.colIdx].column;
|
|
1744
|
+
const laneId = lanes ? lanes[loc.laneIdx].lane.id : undefined;
|
|
1745
|
+
const vertical = ev.key === 'ArrowUp' || ev.key === 'ArrowDown';
|
|
1746
|
+
const down = ev.key === 'ArrowDown';
|
|
1747
|
+
let targetColumn = column;
|
|
1748
|
+
let targetLaneId = laneId;
|
|
1749
|
+
let visibleIndex = Number.MAX_SAFE_INTEGER; // default: fim da célula
|
|
1750
|
+
let destination;
|
|
1751
|
+
if (vertical && ev.shift && lanes) {
|
|
1752
|
+
const targetLane = lanes[loc.laneIdx + (down ? 1 : -1)];
|
|
1753
|
+
if (!targetLane)
|
|
1754
|
+
return;
|
|
1755
|
+
targetLaneId = targetLane.lane.id;
|
|
1756
|
+
destination = `${column.title ?? column.id} · ${targetLane.lane.title ?? targetLane.lane.id}`;
|
|
1757
|
+
}
|
|
1758
|
+
else if (vertical) {
|
|
1759
|
+
const items = this.cellItems(loc.colIdx, loc.laneIdx);
|
|
1760
|
+
const targetVis = loc.visIdx + (down ? 1 : -1);
|
|
1761
|
+
if (targetVis < 0 || targetVis >= items.length)
|
|
1762
|
+
return;
|
|
1763
|
+
visibleIndex = targetVis;
|
|
1764
|
+
destination = `posição ${targetVis + 1}`;
|
|
1765
|
+
}
|
|
1766
|
+
else {
|
|
1767
|
+
const neighbor = this.columnVMs()[loc.colIdx + (ev.key === 'ArrowRight' ? 1 : -1)]?.column;
|
|
1768
|
+
if (!neighbor)
|
|
1769
|
+
return;
|
|
1770
|
+
targetColumn = neighbor;
|
|
1771
|
+
destination = neighbor.title ?? neighbor.id;
|
|
1772
|
+
}
|
|
1773
|
+
const result = executeDrop(board, ev.item.id, targetColumn.id, visibleIndex, targetLaneId);
|
|
1774
|
+
if (result.ok) {
|
|
1775
|
+
const label = (this.getTitle(ev.item) ?? ev.item.id);
|
|
1776
|
+
this.announce(`${label} movido para ${destination}`);
|
|
1777
|
+
this.focusCardSoon(ev.item.id);
|
|
1778
|
+
}
|
|
1779
|
+
else {
|
|
1780
|
+
this.announce(`Movimento recusado: ${result.reason}`);
|
|
1781
|
+
this.moveRejected.emit({
|
|
1782
|
+
itemId: ev.item.id,
|
|
1783
|
+
columnId: targetColumn.id,
|
|
1784
|
+
laneId: targetLaneId,
|
|
1785
|
+
reason: result.reason,
|
|
1786
|
+
});
|
|
1787
|
+
this.focusCardSoon(ev.item.id);
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
getTitle(item) {
|
|
1791
|
+
const title = item['title'];
|
|
1792
|
+
return typeof title === 'string' && title !== '' ? title : undefined;
|
|
1793
|
+
}
|
|
1794
|
+
announce(message) {
|
|
1795
|
+
this.announcement.set(message);
|
|
1796
|
+
}
|
|
1797
|
+
focusCard(id) {
|
|
1798
|
+
// comparação via dataset (CSS.escape não existe em todo ambiente)
|
|
1799
|
+
const cards = this.elementRef.nativeElement.querySelectorAll('[data-item]');
|
|
1800
|
+
for (const el of Array.from(cards)) {
|
|
1801
|
+
if (el.dataset['item'] === id) {
|
|
1802
|
+
el.focus();
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
/** Após um movimento o DOM re-renderiza — refoca no próximo tick. */
|
|
1808
|
+
focusCardSoon(id) {
|
|
1809
|
+
setTimeout(() => this.focusCard(id));
|
|
1810
|
+
}
|
|
1811
|
+
onSearch(text) {
|
|
1812
|
+
const board = this.board();
|
|
1813
|
+
if (!board)
|
|
1814
|
+
return;
|
|
1815
|
+
const others = board.state.filters.filter((f) => !('text' in f));
|
|
1816
|
+
const trimmed = text.trim();
|
|
1817
|
+
board.execute({
|
|
1818
|
+
type: 'setFilters',
|
|
1819
|
+
filters: trimmed ? [...others, { text: trimmed }] : [...others],
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MosKanbanBoard, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1823
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.18", type: MosKanbanBoard, isStandalone: true, selector: "mos-kanban-board", inputs: { board: { classPropertyName: "board", publicName: "board", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, error: { classPropertyName: "error", publicName: "error", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, itemActions: { classPropertyName: "itemActions", publicName: "itemActions", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { itemClick: "itemClick", itemMove: "itemMove", moveRejected: "moveRejected", itemAction: "itemAction", entryAction: "entryAction", dragStarted: "dragStarted", dragEnded: "dragEnded", dragCanceled: "dragCanceled" }, host: { listeners: { "document:click": "closeMenu()", "document:keydown.escape": "onEscape()" } }, providers: [DragCancelFlag], queries: [{ propertyName: "cardDef", first: true, predicate: MosKanbanCardDef, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1824
|
+
@if (error(); as message) {
|
|
1825
|
+
<div class="mkb-state mkb-error" role="alert">
|
|
1826
|
+
<span class="mkb-state-title">Não foi possível carregar o board</span>
|
|
1827
|
+
<span class="mkb-state-hint">{{ message }}</span>
|
|
1828
|
+
</div>
|
|
1829
|
+
} @else if (loading()) {
|
|
1830
|
+
<div class="mkb-state" aria-label="Carregando board" aria-busy="true">
|
|
1831
|
+
<div class="mkb-skeleton">
|
|
1832
|
+
@for (i of [0, 1, 2]; track i) {
|
|
1833
|
+
<div class="mkb-skeleton-col"></div>
|
|
1834
|
+
}
|
|
1835
|
+
</div>
|
|
1836
|
+
</div>
|
|
1837
|
+
} @else if (!board()) {
|
|
1838
|
+
<div class="mkb-state">
|
|
1839
|
+
<span class="mkb-state-title">Nenhum board configurado</span>
|
|
1840
|
+
<span class="mkb-state-hint">mos-kanban-board · v{{ version }}</span>
|
|
1841
|
+
</div>
|
|
1842
|
+
} @else {
|
|
1843
|
+
<div
|
|
1844
|
+
class="mkb-board"
|
|
1845
|
+
[class.mkb-readonly]="readOnly()"
|
|
1846
|
+
role="region"
|
|
1847
|
+
aria-label="Kanban board"
|
|
1848
|
+
>
|
|
1849
|
+
@if (readOnly()) {
|
|
1850
|
+
<span class="mkb-readonly-pill">somente leitura</span>
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
<div class="mkb-sr-only" aria-live="polite">{{ announcement() }}</div>
|
|
1854
|
+
|
|
1855
|
+
@if (showSearch()) {
|
|
1856
|
+
<div class="mkb-search">
|
|
1857
|
+
<input
|
|
1858
|
+
type="search"
|
|
1859
|
+
placeholder="Pesquisar…"
|
|
1860
|
+
aria-label="Pesquisar itens do board"
|
|
1861
|
+
#search
|
|
1862
|
+
(input)="onSearch(search.value)"
|
|
1863
|
+
/>
|
|
1864
|
+
</div>
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
@if (laneVMs(); as lanes) {
|
|
1868
|
+
<div class="mkb-grid" cdkScrollable>
|
|
1869
|
+
<div class="mkb-grid-inner">
|
|
1870
|
+
<div class="mkb-grid-head">
|
|
1871
|
+
<div class="mkb-grid-corner"></div>
|
|
1872
|
+
<div
|
|
1873
|
+
class="mkb-grid-head-cols"
|
|
1874
|
+
cdkDropList
|
|
1875
|
+
cdkDropListOrientation="horizontal"
|
|
1876
|
+
[cdkDropListDisabled]="!columnsReorderable() || readOnly()"
|
|
1877
|
+
(cdkDropListDropped)="onColumnDrop($event)"
|
|
1878
|
+
>
|
|
1879
|
+
@for (vm of columnVMs(); track vm.column.id) {
|
|
1880
|
+
<header
|
|
1881
|
+
class="mkb-grid-col-header"
|
|
1882
|
+
cdkDrag
|
|
1883
|
+
[cdkDragData]="{ kind: 'column', id: vm.column.id }"
|
|
1884
|
+
[cdkDragDisabled]="!columnsReorderable() || readOnly()"
|
|
1885
|
+
(cdkDragStarted)="onLifecycle({ phase: 'start', kind: 'column', id: vm.column.id })"
|
|
1886
|
+
(cdkDragEnded)="onLifecycle({ phase: 'end', kind: 'column', id: vm.column.id })"
|
|
1887
|
+
[class.mkb-col-accented]="!!colColor(vm.column)"
|
|
1888
|
+
[style.--mkb-col-accent]="colColor(vm.column)"
|
|
1889
|
+
[attr.tabindex]="columnsReorderable() ? 0 : null"
|
|
1890
|
+
[attr.data-column-header]="vm.column.id"
|
|
1891
|
+
[attr.aria-keyshortcuts]="columnsReorderable() ? 'Control+ArrowLeft Control+ArrowRight' : null"
|
|
1892
|
+
(keydown)="onColumnHeaderKeydown(vm.column.id, $event)"
|
|
1893
|
+
>
|
|
1894
|
+
@if (columnsReorderable()) {
|
|
1895
|
+
<span class="mkb-col-grip" cdkDragHandle aria-hidden="true">⠿</span>
|
|
1896
|
+
}
|
|
1897
|
+
<span class="mkb-col-title">{{ vm.column.title ?? vm.column.id }}</span>
|
|
1898
|
+
<span class="mkb-col-count" [class.is-full]="isFull(vm.column)">
|
|
1899
|
+
{{ vm.column.count }}{{ vm.column.limit !== undefined ? ' / ' + vm.column.limit : '' }}
|
|
1900
|
+
</span>
|
|
1901
|
+
|
|
1902
|
+
<!-- preview = a COLUNA inteira (fatia vertical da grade
|
|
1903
|
+
não é um elemento DOM único; representação
|
|
1904
|
+
equivalente: header + pilha de cartões-fantasma) -->
|
|
1905
|
+
<div class="mkb-col-slice-preview" *cdkDragPreview>
|
|
1906
|
+
<div class="mkb-col-slice-head">
|
|
1907
|
+
<span class="mkb-col-title">{{ vm.column.title ?? vm.column.id }}</span>
|
|
1908
|
+
<span class="mkb-col-count">{{ vm.column.count }}</span>
|
|
1909
|
+
</div>
|
|
1910
|
+
@for (g of ghostRows(vm.column.count); track $index) {
|
|
1911
|
+
<div class="mkb-col-slice-card"></div>
|
|
1912
|
+
}
|
|
1913
|
+
</div>
|
|
1914
|
+
</header>
|
|
1915
|
+
}
|
|
1916
|
+
</div>
|
|
1917
|
+
</div>
|
|
1918
|
+
|
|
1919
|
+
<div
|
|
1920
|
+
class="mkb-lanes"
|
|
1921
|
+
cdkDropList
|
|
1922
|
+
[cdkDropListDisabled]="!lanesReorderable() || readOnly()"
|
|
1923
|
+
(cdkDropListDropped)="onLaneDrop($event)"
|
|
1924
|
+
>
|
|
1925
|
+
@for (laneVM of lanes; track laneVM.lane.id) {
|
|
1926
|
+
<div
|
|
1927
|
+
class="mkb-lane-row"
|
|
1928
|
+
cdkDrag
|
|
1929
|
+
[cdkDragData]="{ kind: 'lane', id: laneVM.lane.id }"
|
|
1930
|
+
[cdkDragDisabled]="!lanesReorderable() || readOnly()"
|
|
1931
|
+
(cdkDragStarted)="onLifecycle({ phase: 'start', kind: 'lane', id: laneVM.lane.id })"
|
|
1932
|
+
(cdkDragEnded)="onLifecycle({ phase: 'end', kind: 'lane', id: laneVM.lane.id })"
|
|
1933
|
+
>
|
|
1934
|
+
<div
|
|
1935
|
+
class="mkb-lane-header"
|
|
1936
|
+
[attr.tabindex]="lanesReorderable() ? 0 : null"
|
|
1937
|
+
[attr.data-lane-header]="laneVM.lane.id"
|
|
1938
|
+
[attr.aria-keyshortcuts]="lanesReorderable() ? 'Control+ArrowUp Control+ArrowDown' : null"
|
|
1939
|
+
(keydown)="onLaneHeaderKeydown(laneVM.lane.id, $event)"
|
|
1940
|
+
>
|
|
1941
|
+
@if (lanesReorderable()) {
|
|
1942
|
+
<span class="mkb-col-grip" cdkDragHandle aria-hidden="true">⠿</span>
|
|
1943
|
+
}
|
|
1944
|
+
<span class="mkb-col-title">{{ laneVM.lane.title ?? laneVM.lane.id }}</span>
|
|
1945
|
+
<span class="mkb-col-count" [class.is-full]="isFull(laneVM.lane)">
|
|
1946
|
+
{{ laneVM.lane.count }}{{ laneVM.lane.limit !== undefined ? ' / ' + laneVM.lane.limit : '' }}
|
|
1947
|
+
</span>
|
|
1948
|
+
</div>
|
|
1949
|
+
<div class="mkb-lane-cells">
|
|
1950
|
+
@for (cell of laneVM.cells; track cell.column.id) {
|
|
1951
|
+
<mos-kanban-cell
|
|
1952
|
+
class="mkb-grid-cell"
|
|
1953
|
+
[board]="board()!"
|
|
1954
|
+
[columnId]="cell.column.id"
|
|
1955
|
+
[laneId]="laneVM.lane.id"
|
|
1956
|
+
[connectedTo]="connectedFor(cell.column.id, laneVM.lane.id)"
|
|
1957
|
+
[items]="cell.items"
|
|
1958
|
+
[cardTemplate]="cardDef()?.template ?? null"
|
|
1959
|
+
[cardConfig]="cardConfig()"
|
|
1960
|
+
[readOnly]="readOnly()"
|
|
1961
|
+
[selection]="selection()"
|
|
1962
|
+
[allCardIds]="allCardIds()"
|
|
1963
|
+
(cardActivate)="onActivate($event)"
|
|
1964
|
+
(cardContext)="onCardContext($event)"
|
|
1965
|
+
(cardKey)="onCardKey($event)"
|
|
1966
|
+
(cardAction)="itemAction.emit($event)"
|
|
1967
|
+
(entryCheck)="onEntryCheck($event)"
|
|
1968
|
+
(entryAction)="entryAction.emit($event)"
|
|
1969
|
+
(entryMove)="onEntryMove($event)"
|
|
1970
|
+
(lifecycle)="onLifecycle($event)"
|
|
1971
|
+
(dropRejected)="onDropRejected($event)"
|
|
1972
|
+
/>
|
|
1973
|
+
}
|
|
1974
|
+
</div>
|
|
1975
|
+
</div>
|
|
1976
|
+
}
|
|
1977
|
+
</div>
|
|
1978
|
+
</div>
|
|
1979
|
+
</div>
|
|
1980
|
+
} @else {
|
|
1981
|
+
<div
|
|
1982
|
+
class="mkb-columns"
|
|
1983
|
+
cdkDropList
|
|
1984
|
+
cdkDropListOrientation="horizontal"
|
|
1985
|
+
[cdkDropListDisabled]="!columnsReorderable() || readOnly()"
|
|
1986
|
+
(cdkDropListDropped)="onColumnDrop($event)"
|
|
1987
|
+
>
|
|
1988
|
+
@for (vm of columnVMs(); track vm.column.id) {
|
|
1989
|
+
<mos-kanban-column
|
|
1990
|
+
cdkDrag
|
|
1991
|
+
[cdkDragData]="{ kind: 'column', id: vm.column.id }"
|
|
1992
|
+
[cdkDragDisabled]="!columnsReorderable() || readOnly()"
|
|
1993
|
+
[board]="board()!"
|
|
1994
|
+
[column]="vm.column"
|
|
1995
|
+
[items]="vm.items"
|
|
1996
|
+
[cardTemplate]="cardDef()?.template ?? null"
|
|
1997
|
+
[cardConfig]="cardConfig()"
|
|
1998
|
+
[readOnly]="readOnly()"
|
|
1999
|
+
[reorderable]="columnsReorderable() && !readOnly()"
|
|
2000
|
+
[selection]="selection()"
|
|
2001
|
+
[connectedTo]="connectedFor(vm.column.id)"
|
|
2002
|
+
[allCardIds]="allCardIds()"
|
|
2003
|
+
(cardActivate)="onActivate($event)"
|
|
2004
|
+
(cardContext)="onCardContext($event)"
|
|
2005
|
+
(cardKey)="onCardKey($event)"
|
|
2006
|
+
(cardAction)="itemAction.emit($event)"
|
|
2007
|
+
(entryCheck)="onEntryCheck($event)"
|
|
2008
|
+
(entryAction)="entryAction.emit($event)"
|
|
2009
|
+
(entryMove)="onEntryMove($event)"
|
|
2010
|
+
(lifecycle)="onLifecycle($event)"
|
|
2011
|
+
(reorderKey)="onColumnReorderKey(vm.column.id, $event)"
|
|
2012
|
+
(dropRejected)="onDropRejected($event)"
|
|
2013
|
+
(cdkDragStarted)="onLifecycle({ phase: 'start', kind: 'column', id: vm.column.id })"
|
|
2014
|
+
(cdkDragEnded)="onLifecycle({ phase: 'end', kind: 'column', id: vm.column.id })"
|
|
2015
|
+
>
|
|
2016
|
+
@if (columnsReorderable() && !readOnly()) {
|
|
2017
|
+
<span mkbColHandle cdkDragHandle class="mkb-col-grip" aria-hidden="true">⠿</span>
|
|
2018
|
+
}
|
|
2019
|
+
</mos-kanban-column>
|
|
2020
|
+
}
|
|
2021
|
+
</div>
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
@if (menu(); as m) {
|
|
2025
|
+
<div
|
|
2026
|
+
class="mkb-menu"
|
|
2027
|
+
role="menu"
|
|
2028
|
+
[style.left.px]="m.x"
|
|
2029
|
+
[style.top.px]="m.y"
|
|
2030
|
+
(click)="$event.stopPropagation()"
|
|
2031
|
+
>
|
|
2032
|
+
@for (action of itemActions(); track action.id) {
|
|
2033
|
+
<button
|
|
2034
|
+
type="button"
|
|
2035
|
+
role="menuitem"
|
|
2036
|
+
[disabled]="action.disabled"
|
|
2037
|
+
(click)="invoke(m.item, action.id)"
|
|
2038
|
+
>
|
|
2039
|
+
{{ action.label }}
|
|
2040
|
+
</button>
|
|
2041
|
+
}
|
|
2042
|
+
</div>
|
|
2043
|
+
}
|
|
2044
|
+
</div>
|
|
2045
|
+
}
|
|
2046
|
+
`, isInline: true, styles: [":host{display:block;height:100%;font-family:var(--mkb-font, var(--font-sans, system-ui, sans-serif))}.mkb-board{position:relative;display:flex;flex-direction:column;height:100%;background:var(--mkb-surface, var(--color-background, #f6f5f8));border-radius:var(--mkb-radius, var(--radius-md, 10px))}.mkb-search{padding:2px 2px 10px}.mkb-search input{width:min(320px,100%);padding:7px 10px;border:1px solid var(--mkb-border-strong, #c9c4d4);border-radius:var(--mkb-radius-sm, 6px);font:inherit;font-size:13px;background:var(--mkb-card-bg, #fff);color:var(--mkb-foreground, #241f33)}.mkb-search input:focus-visible{outline:none;border-color:var(--mkb-accent, #5e35b1);box-shadow:var(--mkb-focus-ring, 0 0 0 3px rgba(94, 53, 177, .35))}.mkb-columns{display:flex;gap:var(--mkb-column-gap, var(--mkb-gap, 12px));flex:1;min-height:0;overflow-x:auto;padding:2px;align-items:start}.mkb-columns>mos-kanban-column{flex:0 0 var(--mkb-column-width, clamp(220px, 26vw, 320px));max-height:100%}.mkb-grid{flex:1;min-height:0;overflow:auto;padding:2px}.mkb-grid-inner{display:flex;flex-direction:column;gap:var(--mkb-lane-gap, var(--mkb-gap, 12px));width:max-content;min-width:100%}.mkb-lanes{display:flex;flex-direction:column;gap:var(--mkb-lane-gap, var(--mkb-gap, 12px))}.mkb-grid-head,.mkb-lane-row{display:flex;gap:var(--mkb-column-gap, var(--mkb-gap, 12px));align-items:stretch}.mkb-grid-head{position:sticky;top:0;z-index:2}.mkb-grid-head-cols,.mkb-lane-cells{display:flex;gap:var(--mkb-column-gap, var(--mkb-gap, 12px))}.mkb-grid-corner,.mkb-lane-header{flex:0 0 var(--mkb-lane-label-width, 140px)}.mkb-grid-col-header,.mkb-grid-cell{flex:0 0 var(--mkb-grid-column-width, var(--mkb-column-width, clamp(220px, 26vw, 320px)));box-sizing:border-box}.mkb-grid-corner{background:var(--mkb-surface, #f6f5f8)}.mkb-grid-col-header{display:flex;align-items:center;gap:8px;padding:var(--mkb-header-padding, 8px 12px);background:var(--mkb-column-bg, #efedf3);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius-sm, 6px)}.mkb-grid-col-header .mkb-col-title{flex:1}.mkb-grid-col-header[tabindex]:focus-visible,.mkb-lane-header[tabindex]:focus-visible{outline:none;box-shadow:var(--mkb-focus-ring, 0 0 0 3px rgba(94, 53, 177, .35))}.mkb-grid-col-header.mkb-col-accented{background:color-mix(in srgb,var(--mkb-col-accent) 16%,transparent);border-top:3px solid var(--mkb-col-accent)}.mkb-lane-header{display:flex;flex-direction:column;align-items:flex-start;gap:6px;padding:10px;background:var(--mkb-column-bg, #efedf3);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius-sm, 6px)}.mkb-col-grip{cursor:grab;color:var(--mkb-muted, #6f6a7c);font-size:12px;line-height:1;-webkit-user-select:none;user-select:none}.mkb-col-grip:active{cursor:grabbing}.mkb-col-title{font-size:12.5px;font-weight:600;letter-spacing:.2px;color:var(--mkb-foreground, #241f33);overflow-wrap:anywhere}.mkb-col-count{font-size:11px;font-weight:500;padding:1px 8px;border-radius:999px;background:var(--mkb-card-bg, #fff);color:var(--mkb-muted, #6f6a7c)}.mkb-col-count.is-full{color:var(--mkb-danger, #b3261e)}.mkb-grid-cell{background:var(--mkb-column-bg, #efedf3);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius, 10px);min-height:84px}.cdk-drag-placeholder{opacity:.35}.cdk-drop-list-dragging .cdk-drag:not(.cdk-drag-placeholder){transition:transform var(--mkb-dur-move, .18s) var(--mkb-ease, ease)}@media(prefers-reduced-motion:reduce){.cdk-drop-list-dragging .cdk-drag:not(.cdk-drag-placeholder){transition:none}mos-kanban-column.cdk-drag-preview,.mkb-lane-row.cdk-drag-preview{rotate:none}}mos-kanban-column.cdk-drag-preview,.mkb-lane-row.cdk-drag-preview{box-shadow:0 12px 32px #0000003d;opacity:.95}.mkb-col-slice-preview{display:flex;flex-direction:column;gap:var(--mkb-card-gap, 8px);width:var(--mkb-grid-column-width, var(--mkb-column-width, clamp(220px, 26vw, 320px)));padding:0 0 10px;background:var(--mkb-column-bg, #efedf3);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius, 10px);box-shadow:0 12px 32px #0000003d}.mkb-col-slice-head{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--mkb-border, #e3e0ea)}.mkb-col-slice-card{height:44px;margin:0 8px;background:var(--mkb-card-bg, #fff);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius-sm, 6px)}@media(prefers-reduced-motion:reduce){.cdk-drop-list-dragging .cdk-drag:not(.cdk-drag-placeholder){transition:none}}.mkb-readonly-pill{position:absolute;top:-6px;right:8px;z-index:3;font-size:10px;font-weight:600;letter-spacing:.4px;text-transform:uppercase;padding:2px 8px;border-radius:999px;background:var(--mkb-warning, #9a6b00);color:#fff}.mkb-menu{position:fixed;z-index:10;display:flex;flex-direction:column;min-width:160px;padding:4px;background:var(--mkb-card-bg, #fff);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius-sm, 6px);box-shadow:0 8px 24px #00000024}.mkb-menu button{padding:7px 10px;border:0;border-radius:4px;background:transparent;color:var(--mkb-foreground, #241f33);font:inherit;font-size:12.5px;text-align:left;cursor:pointer}.mkb-menu button:hover:not(:disabled){background:color-mix(in srgb,var(--mkb-accent, #5e35b1) 10%,transparent)}.mkb-menu button:disabled{opacity:.45;cursor:default}.mkb-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;min-height:240px;height:100%;background:var(--mkb-surface, var(--color-background, #f6f5f8));border:1px solid var(--mkb-border, var(--color-border, #e3e0ea));border-radius:var(--mkb-radius, var(--radius-md, 10px));color:var(--mkb-muted, var(--color-muted, #6f6a7c))}.mkb-state-title{font-size:14px;font-weight:500}.mkb-error .mkb-state-title{color:var(--mkb-danger, #b3261e)}.mkb-state-hint{font-size:11px;opacity:.8}.mkb-skeleton{display:flex;gap:var(--mkb-gap, 12px)}.mkb-skeleton-col{width:220px;height:180px;border-radius:var(--mkb-radius, 10px);background:var(--mkb-column-bg, #efedf3);animation:mkb-pulse 1.2s ease-in-out infinite}@keyframes mkb-pulse{50%{opacity:.55}}.mkb-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.mkb-skeleton-col{animation:none}}@media(max-width:540px){.mkb-columns>mos-kanban-column{flex:0 0 78vw}.mkb-search input{width:100%}}\n"], dependencies: [{ kind: "directive", type: 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: 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: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: CdkScrollable, selector: "[cdk-scrollable], [cdkScrollable]" }, { kind: "component", type: MosKanbanColumn, selector: "mos-kanban-column", inputs: ["board", "column", "items", "cardTemplate", "cardConfig", "readOnly", "reorderable", "selection", "connectedTo", "allCardIds"], outputs: ["cardActivate", "cardContext", "cardKey", "cardAction", "entryCheck", "entryAction", "entryMove", "lifecycle", "dropRejected", "reorderKey"] }, { kind: "component", type: MosKanbanCell, selector: "mos-kanban-cell", inputs: ["board", "columnId", "laneId", "items", "cardTemplate", "cardConfig", "readOnly", "selection", "connectedTo", "allCardIds"], outputs: ["cardActivate", "cardContext", "cardKey", "cardAction", "entryCheck", "entryAction", "entryMove", "lifecycle", "dropRejected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2047
|
+
}
|
|
2048
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.18", ngImport: i0, type: MosKanbanBoard, decorators: [{
|
|
2049
|
+
type: Component,
|
|
2050
|
+
args: [{ selector: 'mos-kanban-board', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
2051
|
+
CdkDropList,
|
|
2052
|
+
CdkDrag,
|
|
2053
|
+
CdkDragHandle,
|
|
2054
|
+
CdkDragPreview,
|
|
2055
|
+
CdkScrollable,
|
|
2056
|
+
MosKanbanColumn,
|
|
2057
|
+
MosKanbanCell,
|
|
2058
|
+
], providers: [DragCancelFlag], host: {
|
|
2059
|
+
'(document:click)': 'closeMenu()',
|
|
2060
|
+
'(document:keydown.escape)': 'onEscape()',
|
|
2061
|
+
}, template: `
|
|
2062
|
+
@if (error(); as message) {
|
|
2063
|
+
<div class="mkb-state mkb-error" role="alert">
|
|
2064
|
+
<span class="mkb-state-title">Não foi possível carregar o board</span>
|
|
2065
|
+
<span class="mkb-state-hint">{{ message }}</span>
|
|
2066
|
+
</div>
|
|
2067
|
+
} @else if (loading()) {
|
|
2068
|
+
<div class="mkb-state" aria-label="Carregando board" aria-busy="true">
|
|
2069
|
+
<div class="mkb-skeleton">
|
|
2070
|
+
@for (i of [0, 1, 2]; track i) {
|
|
2071
|
+
<div class="mkb-skeleton-col"></div>
|
|
2072
|
+
}
|
|
2073
|
+
</div>
|
|
2074
|
+
</div>
|
|
2075
|
+
} @else if (!board()) {
|
|
2076
|
+
<div class="mkb-state">
|
|
2077
|
+
<span class="mkb-state-title">Nenhum board configurado</span>
|
|
2078
|
+
<span class="mkb-state-hint">mos-kanban-board · v{{ version }}</span>
|
|
2079
|
+
</div>
|
|
2080
|
+
} @else {
|
|
2081
|
+
<div
|
|
2082
|
+
class="mkb-board"
|
|
2083
|
+
[class.mkb-readonly]="readOnly()"
|
|
2084
|
+
role="region"
|
|
2085
|
+
aria-label="Kanban board"
|
|
2086
|
+
>
|
|
2087
|
+
@if (readOnly()) {
|
|
2088
|
+
<span class="mkb-readonly-pill">somente leitura</span>
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
<div class="mkb-sr-only" aria-live="polite">{{ announcement() }}</div>
|
|
2092
|
+
|
|
2093
|
+
@if (showSearch()) {
|
|
2094
|
+
<div class="mkb-search">
|
|
2095
|
+
<input
|
|
2096
|
+
type="search"
|
|
2097
|
+
placeholder="Pesquisar…"
|
|
2098
|
+
aria-label="Pesquisar itens do board"
|
|
2099
|
+
#search
|
|
2100
|
+
(input)="onSearch(search.value)"
|
|
2101
|
+
/>
|
|
2102
|
+
</div>
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
@if (laneVMs(); as lanes) {
|
|
2106
|
+
<div class="mkb-grid" cdkScrollable>
|
|
2107
|
+
<div class="mkb-grid-inner">
|
|
2108
|
+
<div class="mkb-grid-head">
|
|
2109
|
+
<div class="mkb-grid-corner"></div>
|
|
2110
|
+
<div
|
|
2111
|
+
class="mkb-grid-head-cols"
|
|
2112
|
+
cdkDropList
|
|
2113
|
+
cdkDropListOrientation="horizontal"
|
|
2114
|
+
[cdkDropListDisabled]="!columnsReorderable() || readOnly()"
|
|
2115
|
+
(cdkDropListDropped)="onColumnDrop($event)"
|
|
2116
|
+
>
|
|
2117
|
+
@for (vm of columnVMs(); track vm.column.id) {
|
|
2118
|
+
<header
|
|
2119
|
+
class="mkb-grid-col-header"
|
|
2120
|
+
cdkDrag
|
|
2121
|
+
[cdkDragData]="{ kind: 'column', id: vm.column.id }"
|
|
2122
|
+
[cdkDragDisabled]="!columnsReorderable() || readOnly()"
|
|
2123
|
+
(cdkDragStarted)="onLifecycle({ phase: 'start', kind: 'column', id: vm.column.id })"
|
|
2124
|
+
(cdkDragEnded)="onLifecycle({ phase: 'end', kind: 'column', id: vm.column.id })"
|
|
2125
|
+
[class.mkb-col-accented]="!!colColor(vm.column)"
|
|
2126
|
+
[style.--mkb-col-accent]="colColor(vm.column)"
|
|
2127
|
+
[attr.tabindex]="columnsReorderable() ? 0 : null"
|
|
2128
|
+
[attr.data-column-header]="vm.column.id"
|
|
2129
|
+
[attr.aria-keyshortcuts]="columnsReorderable() ? 'Control+ArrowLeft Control+ArrowRight' : null"
|
|
2130
|
+
(keydown)="onColumnHeaderKeydown(vm.column.id, $event)"
|
|
2131
|
+
>
|
|
2132
|
+
@if (columnsReorderable()) {
|
|
2133
|
+
<span class="mkb-col-grip" cdkDragHandle aria-hidden="true">⠿</span>
|
|
2134
|
+
}
|
|
2135
|
+
<span class="mkb-col-title">{{ vm.column.title ?? vm.column.id }}</span>
|
|
2136
|
+
<span class="mkb-col-count" [class.is-full]="isFull(vm.column)">
|
|
2137
|
+
{{ vm.column.count }}{{ vm.column.limit !== undefined ? ' / ' + vm.column.limit : '' }}
|
|
2138
|
+
</span>
|
|
2139
|
+
|
|
2140
|
+
<!-- preview = a COLUNA inteira (fatia vertical da grade
|
|
2141
|
+
não é um elemento DOM único; representação
|
|
2142
|
+
equivalente: header + pilha de cartões-fantasma) -->
|
|
2143
|
+
<div class="mkb-col-slice-preview" *cdkDragPreview>
|
|
2144
|
+
<div class="mkb-col-slice-head">
|
|
2145
|
+
<span class="mkb-col-title">{{ vm.column.title ?? vm.column.id }}</span>
|
|
2146
|
+
<span class="mkb-col-count">{{ vm.column.count }}</span>
|
|
2147
|
+
</div>
|
|
2148
|
+
@for (g of ghostRows(vm.column.count); track $index) {
|
|
2149
|
+
<div class="mkb-col-slice-card"></div>
|
|
2150
|
+
}
|
|
2151
|
+
</div>
|
|
2152
|
+
</header>
|
|
2153
|
+
}
|
|
2154
|
+
</div>
|
|
2155
|
+
</div>
|
|
2156
|
+
|
|
2157
|
+
<div
|
|
2158
|
+
class="mkb-lanes"
|
|
2159
|
+
cdkDropList
|
|
2160
|
+
[cdkDropListDisabled]="!lanesReorderable() || readOnly()"
|
|
2161
|
+
(cdkDropListDropped)="onLaneDrop($event)"
|
|
2162
|
+
>
|
|
2163
|
+
@for (laneVM of lanes; track laneVM.lane.id) {
|
|
2164
|
+
<div
|
|
2165
|
+
class="mkb-lane-row"
|
|
2166
|
+
cdkDrag
|
|
2167
|
+
[cdkDragData]="{ kind: 'lane', id: laneVM.lane.id }"
|
|
2168
|
+
[cdkDragDisabled]="!lanesReorderable() || readOnly()"
|
|
2169
|
+
(cdkDragStarted)="onLifecycle({ phase: 'start', kind: 'lane', id: laneVM.lane.id })"
|
|
2170
|
+
(cdkDragEnded)="onLifecycle({ phase: 'end', kind: 'lane', id: laneVM.lane.id })"
|
|
2171
|
+
>
|
|
2172
|
+
<div
|
|
2173
|
+
class="mkb-lane-header"
|
|
2174
|
+
[attr.tabindex]="lanesReorderable() ? 0 : null"
|
|
2175
|
+
[attr.data-lane-header]="laneVM.lane.id"
|
|
2176
|
+
[attr.aria-keyshortcuts]="lanesReorderable() ? 'Control+ArrowUp Control+ArrowDown' : null"
|
|
2177
|
+
(keydown)="onLaneHeaderKeydown(laneVM.lane.id, $event)"
|
|
2178
|
+
>
|
|
2179
|
+
@if (lanesReorderable()) {
|
|
2180
|
+
<span class="mkb-col-grip" cdkDragHandle aria-hidden="true">⠿</span>
|
|
2181
|
+
}
|
|
2182
|
+
<span class="mkb-col-title">{{ laneVM.lane.title ?? laneVM.lane.id }}</span>
|
|
2183
|
+
<span class="mkb-col-count" [class.is-full]="isFull(laneVM.lane)">
|
|
2184
|
+
{{ laneVM.lane.count }}{{ laneVM.lane.limit !== undefined ? ' / ' + laneVM.lane.limit : '' }}
|
|
2185
|
+
</span>
|
|
2186
|
+
</div>
|
|
2187
|
+
<div class="mkb-lane-cells">
|
|
2188
|
+
@for (cell of laneVM.cells; track cell.column.id) {
|
|
2189
|
+
<mos-kanban-cell
|
|
2190
|
+
class="mkb-grid-cell"
|
|
2191
|
+
[board]="board()!"
|
|
2192
|
+
[columnId]="cell.column.id"
|
|
2193
|
+
[laneId]="laneVM.lane.id"
|
|
2194
|
+
[connectedTo]="connectedFor(cell.column.id, laneVM.lane.id)"
|
|
2195
|
+
[items]="cell.items"
|
|
2196
|
+
[cardTemplate]="cardDef()?.template ?? null"
|
|
2197
|
+
[cardConfig]="cardConfig()"
|
|
2198
|
+
[readOnly]="readOnly()"
|
|
2199
|
+
[selection]="selection()"
|
|
2200
|
+
[allCardIds]="allCardIds()"
|
|
2201
|
+
(cardActivate)="onActivate($event)"
|
|
2202
|
+
(cardContext)="onCardContext($event)"
|
|
2203
|
+
(cardKey)="onCardKey($event)"
|
|
2204
|
+
(cardAction)="itemAction.emit($event)"
|
|
2205
|
+
(entryCheck)="onEntryCheck($event)"
|
|
2206
|
+
(entryAction)="entryAction.emit($event)"
|
|
2207
|
+
(entryMove)="onEntryMove($event)"
|
|
2208
|
+
(lifecycle)="onLifecycle($event)"
|
|
2209
|
+
(dropRejected)="onDropRejected($event)"
|
|
2210
|
+
/>
|
|
2211
|
+
}
|
|
2212
|
+
</div>
|
|
2213
|
+
</div>
|
|
2214
|
+
}
|
|
2215
|
+
</div>
|
|
2216
|
+
</div>
|
|
2217
|
+
</div>
|
|
2218
|
+
} @else {
|
|
2219
|
+
<div
|
|
2220
|
+
class="mkb-columns"
|
|
2221
|
+
cdkDropList
|
|
2222
|
+
cdkDropListOrientation="horizontal"
|
|
2223
|
+
[cdkDropListDisabled]="!columnsReorderable() || readOnly()"
|
|
2224
|
+
(cdkDropListDropped)="onColumnDrop($event)"
|
|
2225
|
+
>
|
|
2226
|
+
@for (vm of columnVMs(); track vm.column.id) {
|
|
2227
|
+
<mos-kanban-column
|
|
2228
|
+
cdkDrag
|
|
2229
|
+
[cdkDragData]="{ kind: 'column', id: vm.column.id }"
|
|
2230
|
+
[cdkDragDisabled]="!columnsReorderable() || readOnly()"
|
|
2231
|
+
[board]="board()!"
|
|
2232
|
+
[column]="vm.column"
|
|
2233
|
+
[items]="vm.items"
|
|
2234
|
+
[cardTemplate]="cardDef()?.template ?? null"
|
|
2235
|
+
[cardConfig]="cardConfig()"
|
|
2236
|
+
[readOnly]="readOnly()"
|
|
2237
|
+
[reorderable]="columnsReorderable() && !readOnly()"
|
|
2238
|
+
[selection]="selection()"
|
|
2239
|
+
[connectedTo]="connectedFor(vm.column.id)"
|
|
2240
|
+
[allCardIds]="allCardIds()"
|
|
2241
|
+
(cardActivate)="onActivate($event)"
|
|
2242
|
+
(cardContext)="onCardContext($event)"
|
|
2243
|
+
(cardKey)="onCardKey($event)"
|
|
2244
|
+
(cardAction)="itemAction.emit($event)"
|
|
2245
|
+
(entryCheck)="onEntryCheck($event)"
|
|
2246
|
+
(entryAction)="entryAction.emit($event)"
|
|
2247
|
+
(entryMove)="onEntryMove($event)"
|
|
2248
|
+
(lifecycle)="onLifecycle($event)"
|
|
2249
|
+
(reorderKey)="onColumnReorderKey(vm.column.id, $event)"
|
|
2250
|
+
(dropRejected)="onDropRejected($event)"
|
|
2251
|
+
(cdkDragStarted)="onLifecycle({ phase: 'start', kind: 'column', id: vm.column.id })"
|
|
2252
|
+
(cdkDragEnded)="onLifecycle({ phase: 'end', kind: 'column', id: vm.column.id })"
|
|
2253
|
+
>
|
|
2254
|
+
@if (columnsReorderable() && !readOnly()) {
|
|
2255
|
+
<span mkbColHandle cdkDragHandle class="mkb-col-grip" aria-hidden="true">⠿</span>
|
|
2256
|
+
}
|
|
2257
|
+
</mos-kanban-column>
|
|
2258
|
+
}
|
|
2259
|
+
</div>
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
@if (menu(); as m) {
|
|
2263
|
+
<div
|
|
2264
|
+
class="mkb-menu"
|
|
2265
|
+
role="menu"
|
|
2266
|
+
[style.left.px]="m.x"
|
|
2267
|
+
[style.top.px]="m.y"
|
|
2268
|
+
(click)="$event.stopPropagation()"
|
|
2269
|
+
>
|
|
2270
|
+
@for (action of itemActions(); track action.id) {
|
|
2271
|
+
<button
|
|
2272
|
+
type="button"
|
|
2273
|
+
role="menuitem"
|
|
2274
|
+
[disabled]="action.disabled"
|
|
2275
|
+
(click)="invoke(m.item, action.id)"
|
|
2276
|
+
>
|
|
2277
|
+
{{ action.label }}
|
|
2278
|
+
</button>
|
|
2279
|
+
}
|
|
2280
|
+
</div>
|
|
2281
|
+
}
|
|
2282
|
+
</div>
|
|
2283
|
+
}
|
|
2284
|
+
`, styles: [":host{display:block;height:100%;font-family:var(--mkb-font, var(--font-sans, system-ui, sans-serif))}.mkb-board{position:relative;display:flex;flex-direction:column;height:100%;background:var(--mkb-surface, var(--color-background, #f6f5f8));border-radius:var(--mkb-radius, var(--radius-md, 10px))}.mkb-search{padding:2px 2px 10px}.mkb-search input{width:min(320px,100%);padding:7px 10px;border:1px solid var(--mkb-border-strong, #c9c4d4);border-radius:var(--mkb-radius-sm, 6px);font:inherit;font-size:13px;background:var(--mkb-card-bg, #fff);color:var(--mkb-foreground, #241f33)}.mkb-search input:focus-visible{outline:none;border-color:var(--mkb-accent, #5e35b1);box-shadow:var(--mkb-focus-ring, 0 0 0 3px rgba(94, 53, 177, .35))}.mkb-columns{display:flex;gap:var(--mkb-column-gap, var(--mkb-gap, 12px));flex:1;min-height:0;overflow-x:auto;padding:2px;align-items:start}.mkb-columns>mos-kanban-column{flex:0 0 var(--mkb-column-width, clamp(220px, 26vw, 320px));max-height:100%}.mkb-grid{flex:1;min-height:0;overflow:auto;padding:2px}.mkb-grid-inner{display:flex;flex-direction:column;gap:var(--mkb-lane-gap, var(--mkb-gap, 12px));width:max-content;min-width:100%}.mkb-lanes{display:flex;flex-direction:column;gap:var(--mkb-lane-gap, var(--mkb-gap, 12px))}.mkb-grid-head,.mkb-lane-row{display:flex;gap:var(--mkb-column-gap, var(--mkb-gap, 12px));align-items:stretch}.mkb-grid-head{position:sticky;top:0;z-index:2}.mkb-grid-head-cols,.mkb-lane-cells{display:flex;gap:var(--mkb-column-gap, var(--mkb-gap, 12px))}.mkb-grid-corner,.mkb-lane-header{flex:0 0 var(--mkb-lane-label-width, 140px)}.mkb-grid-col-header,.mkb-grid-cell{flex:0 0 var(--mkb-grid-column-width, var(--mkb-column-width, clamp(220px, 26vw, 320px)));box-sizing:border-box}.mkb-grid-corner{background:var(--mkb-surface, #f6f5f8)}.mkb-grid-col-header{display:flex;align-items:center;gap:8px;padding:var(--mkb-header-padding, 8px 12px);background:var(--mkb-column-bg, #efedf3);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius-sm, 6px)}.mkb-grid-col-header .mkb-col-title{flex:1}.mkb-grid-col-header[tabindex]:focus-visible,.mkb-lane-header[tabindex]:focus-visible{outline:none;box-shadow:var(--mkb-focus-ring, 0 0 0 3px rgba(94, 53, 177, .35))}.mkb-grid-col-header.mkb-col-accented{background:color-mix(in srgb,var(--mkb-col-accent) 16%,transparent);border-top:3px solid var(--mkb-col-accent)}.mkb-lane-header{display:flex;flex-direction:column;align-items:flex-start;gap:6px;padding:10px;background:var(--mkb-column-bg, #efedf3);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius-sm, 6px)}.mkb-col-grip{cursor:grab;color:var(--mkb-muted, #6f6a7c);font-size:12px;line-height:1;-webkit-user-select:none;user-select:none}.mkb-col-grip:active{cursor:grabbing}.mkb-col-title{font-size:12.5px;font-weight:600;letter-spacing:.2px;color:var(--mkb-foreground, #241f33);overflow-wrap:anywhere}.mkb-col-count{font-size:11px;font-weight:500;padding:1px 8px;border-radius:999px;background:var(--mkb-card-bg, #fff);color:var(--mkb-muted, #6f6a7c)}.mkb-col-count.is-full{color:var(--mkb-danger, #b3261e)}.mkb-grid-cell{background:var(--mkb-column-bg, #efedf3);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius, 10px);min-height:84px}.cdk-drag-placeholder{opacity:.35}.cdk-drop-list-dragging .cdk-drag:not(.cdk-drag-placeholder){transition:transform var(--mkb-dur-move, .18s) var(--mkb-ease, ease)}@media(prefers-reduced-motion:reduce){.cdk-drop-list-dragging .cdk-drag:not(.cdk-drag-placeholder){transition:none}mos-kanban-column.cdk-drag-preview,.mkb-lane-row.cdk-drag-preview{rotate:none}}mos-kanban-column.cdk-drag-preview,.mkb-lane-row.cdk-drag-preview{box-shadow:0 12px 32px #0000003d;opacity:.95}.mkb-col-slice-preview{display:flex;flex-direction:column;gap:var(--mkb-card-gap, 8px);width:var(--mkb-grid-column-width, var(--mkb-column-width, clamp(220px, 26vw, 320px)));padding:0 0 10px;background:var(--mkb-column-bg, #efedf3);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius, 10px);box-shadow:0 12px 32px #0000003d}.mkb-col-slice-head{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;border-bottom:1px solid var(--mkb-border, #e3e0ea)}.mkb-col-slice-card{height:44px;margin:0 8px;background:var(--mkb-card-bg, #fff);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius-sm, 6px)}@media(prefers-reduced-motion:reduce){.cdk-drop-list-dragging .cdk-drag:not(.cdk-drag-placeholder){transition:none}}.mkb-readonly-pill{position:absolute;top:-6px;right:8px;z-index:3;font-size:10px;font-weight:600;letter-spacing:.4px;text-transform:uppercase;padding:2px 8px;border-radius:999px;background:var(--mkb-warning, #9a6b00);color:#fff}.mkb-menu{position:fixed;z-index:10;display:flex;flex-direction:column;min-width:160px;padding:4px;background:var(--mkb-card-bg, #fff);border:1px solid var(--mkb-border, #e3e0ea);border-radius:var(--mkb-radius-sm, 6px);box-shadow:0 8px 24px #00000024}.mkb-menu button{padding:7px 10px;border:0;border-radius:4px;background:transparent;color:var(--mkb-foreground, #241f33);font:inherit;font-size:12.5px;text-align:left;cursor:pointer}.mkb-menu button:hover:not(:disabled){background:color-mix(in srgb,var(--mkb-accent, #5e35b1) 10%,transparent)}.mkb-menu button:disabled{opacity:.45;cursor:default}.mkb-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;min-height:240px;height:100%;background:var(--mkb-surface, var(--color-background, #f6f5f8));border:1px solid var(--mkb-border, var(--color-border, #e3e0ea));border-radius:var(--mkb-radius, var(--radius-md, 10px));color:var(--mkb-muted, var(--color-muted, #6f6a7c))}.mkb-state-title{font-size:14px;font-weight:500}.mkb-error .mkb-state-title{color:var(--mkb-danger, #b3261e)}.mkb-state-hint{font-size:11px;opacity:.8}.mkb-skeleton{display:flex;gap:var(--mkb-gap, 12px)}.mkb-skeleton-col{width:220px;height:180px;border-radius:var(--mkb-radius, 10px);background:var(--mkb-column-bg, #efedf3);animation:mkb-pulse 1.2s ease-in-out infinite}@keyframes mkb-pulse{50%{opacity:.55}}.mkb-sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}@media(prefers-reduced-motion:reduce){.mkb-skeleton-col{animation:none}}@media(max-width:540px){.mkb-columns>mos-kanban-column{flex:0 0 78vw}.mkb-search input{width:100%}}\n"] }]
|
|
2285
|
+
}], ctorParameters: () => [], propDecorators: { board: [{ type: i0.Input, args: [{ isSignal: true, alias: "board", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], itemActions: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemActions", required: false }] }], itemClick: [{ type: i0.Output, args: ["itemClick"] }], itemMove: [{ type: i0.Output, args: ["itemMove"] }], moveRejected: [{ type: i0.Output, args: ["moveRejected"] }], itemAction: [{ type: i0.Output, args: ["itemAction"] }], entryAction: [{ type: i0.Output, args: ["entryAction"] }], dragStarted: [{ type: i0.Output, args: ["dragStarted"] }], dragEnded: [{ type: i0.Output, args: ["dragEnded"] }], dragCanceled: [{ type: i0.Output, args: ["dragCanceled"] }], cardDef: [{ type: i0.ContentChild, args: [i0.forwardRef(() => MosKanbanCardDef), { isSignal: true }] }] } });
|
|
2286
|
+
|
|
2287
|
+
/*
|
|
2288
|
+
* @mosaicoo/kanban/ui — camada visual Angular.
|
|
2289
|
+
*
|
|
2290
|
+
* Consome exclusivamente a API pública do core (docs/01-arquitetura.md §2);
|
|
2291
|
+
* nenhuma regra de negócio ou de domínio vive aqui. Tema por design tokens
|
|
2292
|
+
* `--mkb-*` com fallback para os tokens do DS Mosaicoo (§12).
|
|
2293
|
+
*/
|
|
2294
|
+
|
|
2295
|
+
/**
|
|
2296
|
+
* Generated bundle index. Do not edit.
|
|
2297
|
+
*/
|
|
2298
|
+
|
|
2299
|
+
export { DragCancelFlag, MosKanbanBoard, MosKanbanCard, MosKanbanCardBlocks, MosKanbanCardDef, MosKanbanCell, MosKanbanColumn, canEnterCell, cellListId, entriesListId, executeDrop, formatDate, formatNumber, formatRelative, formatText, initials, resolveDropIndex, resolveIcon };
|