@object-ui/plugin-kanban 0.5.0 → 3.0.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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +34 -0
- package/dist/{KanbanEnhanced-BqDEu7Z6.js → KanbanEnhanced-BPIKjTDv.js} +7 -7
- package/dist/KanbanImpl-BfOKAnJS.js +194 -0
- package/dist/{index-CrR06na7.js → index-CWGTi2xn.js} +253 -215
- package/dist/index.js +1 -1
- package/dist/index.umd.cjs +4 -4
- package/dist/{sortable.esm-ZHwgFQIO.js → sortable.esm-CNNHgHk5.js} +1 -0
- package/dist/src/KanbanImpl.d.ts +2 -1
- package/dist/src/KanbanImpl.d.ts.map +1 -1
- package/dist/src/ObjectKanban.EdgeCases.stories.d.ts +26 -0
- package/dist/src/ObjectKanban.EdgeCases.stories.d.ts.map +1 -0
- package/dist/src/ObjectKanban.d.ts +2 -0
- package/dist/src/ObjectKanban.d.ts.map +1 -1
- package/dist/src/ObjectKanban.stories.d.ts +24 -0
- package/dist/src/ObjectKanban.stories.d.ts.map +1 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +10 -10
- package/src/KanbanImpl.tsx +82 -20
- package/src/ObjectKanban.EdgeCases.stories.tsx +168 -0
- package/src/ObjectKanban.stories.tsx +152 -0
- package/src/ObjectKanban.tsx +43 -2
- package/src/__tests__/KanbanEnhanced.test.tsx +1 -0
- package/src/__tests__/accessibility.test.tsx +296 -0
- package/src/__tests__/dnd-undo-integration.test.tsx +525 -0
- package/src/__tests__/performance-benchmark.test.tsx +306 -0
- package/src/__tests__/view-states.test.tsx +403 -0
- package/src/index.test.ts +8 -8
- package/src/index.tsx +29 -4
- package/dist/KanbanImpl-B8nu2BvG.js +0 -144
package/src/index.test.ts
CHANGED
|
@@ -17,12 +17,12 @@ describe('Plugin Kanban', () => {
|
|
|
17
17
|
|
|
18
18
|
describe('kanban component', () => {
|
|
19
19
|
it('should be registered in ComponentRegistry', () => {
|
|
20
|
-
const kanbanRenderer = ComponentRegistry.get('kanban');
|
|
20
|
+
const kanbanRenderer = ComponentRegistry.get('kanban-ui');
|
|
21
21
|
expect(kanbanRenderer).toBeDefined();
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
it('should have proper metadata', () => {
|
|
25
|
-
const config = ComponentRegistry.getConfig('kanban');
|
|
25
|
+
const config = ComponentRegistry.getConfig('kanban-ui');
|
|
26
26
|
expect(config).toBeDefined();
|
|
27
27
|
expect(config?.label).toBe('Kanban Board');
|
|
28
28
|
expect(config?.icon).toBe('LayoutDashboard');
|
|
@@ -32,7 +32,7 @@ describe('Plugin Kanban', () => {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
it('should have expected inputs', () => {
|
|
35
|
-
const config = ComponentRegistry.getConfig('kanban');
|
|
35
|
+
const config = ComponentRegistry.getConfig('kanban-ui');
|
|
36
36
|
const inputNames = config?.inputs?.map((input: any) => input.name) || [];
|
|
37
37
|
|
|
38
38
|
expect(inputNames).toContain('columns');
|
|
@@ -41,7 +41,7 @@ describe('Plugin Kanban', () => {
|
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
it('should have columns as required input', () => {
|
|
44
|
-
const config = ComponentRegistry.getConfig('kanban');
|
|
44
|
+
const config = ComponentRegistry.getConfig('kanban-ui');
|
|
45
45
|
const columnsInput = config?.inputs?.find((input: any) => input.name === 'columns');
|
|
46
46
|
|
|
47
47
|
expect(columnsInput).toBeDefined();
|
|
@@ -51,7 +51,7 @@ describe('Plugin Kanban', () => {
|
|
|
51
51
|
});
|
|
52
52
|
|
|
53
53
|
it('should have onCardMove as code input', () => {
|
|
54
|
-
const config = ComponentRegistry.getConfig('kanban');
|
|
54
|
+
const config = ComponentRegistry.getConfig('kanban-ui');
|
|
55
55
|
const onCardMoveInput = config?.inputs?.find((input: any) => input.name === 'onCardMove');
|
|
56
56
|
|
|
57
57
|
expect(onCardMoveInput).toBeDefined();
|
|
@@ -61,7 +61,7 @@ describe('Plugin Kanban', () => {
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
it('should have sensible default props', () => {
|
|
64
|
-
const config = ComponentRegistry.getConfig('kanban');
|
|
64
|
+
const config = ComponentRegistry.getConfig('kanban-ui');
|
|
65
65
|
const defaults = config?.defaultProps;
|
|
66
66
|
|
|
67
67
|
expect(defaults).toBeDefined();
|
|
@@ -72,7 +72,7 @@ describe('Plugin Kanban', () => {
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
it('should have default columns with proper structure', () => {
|
|
75
|
-
const config = ComponentRegistry.getConfig('kanban');
|
|
75
|
+
const config = ComponentRegistry.getConfig('kanban-ui');
|
|
76
76
|
const defaults = config?.defaultProps;
|
|
77
77
|
const columns = defaults?.columns || [];
|
|
78
78
|
|
|
@@ -93,7 +93,7 @@ describe('Plugin Kanban', () => {
|
|
|
93
93
|
});
|
|
94
94
|
|
|
95
95
|
it('should have cards with proper structure', () => {
|
|
96
|
-
const config = ComponentRegistry.getConfig('kanban');
|
|
96
|
+
const config = ComponentRegistry.getConfig('kanban-ui');
|
|
97
97
|
const defaults = config?.defaultProps;
|
|
98
98
|
const columns = defaults?.columns || [];
|
|
99
99
|
|
package/src/index.tsx
CHANGED
|
@@ -30,6 +30,7 @@ export interface KanbanRendererProps {
|
|
|
30
30
|
data?: Array<any>;
|
|
31
31
|
groupBy?: string;
|
|
32
32
|
onCardMove?: (cardId: string, fromColumnId: string, toColumnId: string, newIndex: number) => void;
|
|
33
|
+
onCardClick?: (card: any) => void;
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -44,9 +45,18 @@ export const KanbanRenderer: React.FC<KanbanRendererProps> = ({ schema }) => {
|
|
|
44
45
|
|
|
45
46
|
// If we have flat data and a grouping key, distribute items into columns
|
|
46
47
|
if (data && groupBy && Array.isArray(data)) {
|
|
47
|
-
//
|
|
48
|
+
// Build label→id mapping so data values (labels like "In Progress")
|
|
49
|
+
// match column IDs (option values like "in_progress")
|
|
50
|
+
const labelToColumnId: Record<string, string> = {};
|
|
51
|
+
columns.forEach((col: any) => {
|
|
52
|
+
if (col.id) labelToColumnId[String(col.id).toLowerCase()] = col.id;
|
|
53
|
+
if (col.title) labelToColumnId[String(col.title).toLowerCase()] = col.id;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// 1. Group data by key, normalizing via label→id mapping
|
|
48
57
|
const groups = data.reduce((acc, item) => {
|
|
49
|
-
const
|
|
58
|
+
const rawKey = String(item[groupBy] ?? '');
|
|
59
|
+
const key = labelToColumnId[rawKey.toLowerCase()] ?? rawKey;
|
|
50
60
|
if (!acc[key]) acc[key] = [];
|
|
51
61
|
acc[key].push(item);
|
|
52
62
|
return acc;
|
|
@@ -71,6 +81,7 @@ export const KanbanRenderer: React.FC<KanbanRendererProps> = ({ schema }) => {
|
|
|
71
81
|
<LazyKanban
|
|
72
82
|
columns={processedColumns}
|
|
73
83
|
onCardMove={schema.onCardMove}
|
|
84
|
+
onCardClick={schema.onCardClick}
|
|
74
85
|
className={schema.className}
|
|
75
86
|
/>
|
|
76
87
|
</Suspense>
|
|
@@ -79,7 +90,7 @@ export const KanbanRenderer: React.FC<KanbanRendererProps> = ({ schema }) => {
|
|
|
79
90
|
|
|
80
91
|
// Register the component with the ComponentRegistry
|
|
81
92
|
ComponentRegistry.register(
|
|
82
|
-
'kanban',
|
|
93
|
+
'kanban-ui',
|
|
83
94
|
KanbanRenderer,
|
|
84
95
|
{
|
|
85
96
|
namespace: 'plugin-kanban',
|
|
@@ -250,7 +261,21 @@ ComponentRegistry.register(
|
|
|
250
261
|
{
|
|
251
262
|
namespace: 'plugin-kanban',
|
|
252
263
|
label: 'Object Kanban',
|
|
253
|
-
category: '
|
|
264
|
+
category: 'view',
|
|
265
|
+
inputs: [
|
|
266
|
+
{ name: 'objectName', type: 'string', label: 'Object Name', required: true },
|
|
267
|
+
{ name: 'columns', type: 'array', label: 'Columns' }
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
ComponentRegistry.register(
|
|
273
|
+
'kanban',
|
|
274
|
+
ObjectKanbanRenderer,
|
|
275
|
+
{
|
|
276
|
+
namespace: 'view',
|
|
277
|
+
label: 'Kanban Board',
|
|
278
|
+
category: 'view',
|
|
254
279
|
inputs: [
|
|
255
280
|
{ name: 'objectName', type: 'string', label: 'Object Name', required: true },
|
|
256
281
|
{ name: 'columns', type: 'array', label: 'Columns' }
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import { j as e } from "./index-CrR06na7.js";
|
|
2
|
-
import * as x from "react";
|
|
3
|
-
import { u as L, a as P, D as T, c as F, b as H, d as M, P as $, e as k, S as q, v as z, C as G } from "./sortable.esm-ZHwgFQIO.js";
|
|
4
|
-
import { Badge as B, ScrollArea as J, Card as Q, CardHeader as U, CardTitle as V, CardDescription as W, CardContent as X } from "@object-ui/components";
|
|
5
|
-
const E = (...s) => s.filter(Boolean).join(" ");
|
|
6
|
-
function R({ card: s }) {
|
|
7
|
-
const {
|
|
8
|
-
attributes: f,
|
|
9
|
-
listeners: d,
|
|
10
|
-
setNodeRef: u,
|
|
11
|
-
transform: p,
|
|
12
|
-
transition: t,
|
|
13
|
-
isDragging: n
|
|
14
|
-
} = k({ id: s.id }), b = {
|
|
15
|
-
transform: G.Transform.toString(p),
|
|
16
|
-
transition: t,
|
|
17
|
-
opacity: n ? 0.5 : void 0
|
|
18
|
-
};
|
|
19
|
-
return /* @__PURE__ */ e.jsx("div", { ref: u, style: b, ...f, ...d, children: /* @__PURE__ */ e.jsxs(Q, { className: "mb-2 cursor-grab active:cursor-grabbing border-border bg-card/60 hover:border-primary/40 hover:shadow-lg hover:shadow-primary/10 transition-all duration-300 group", children: [
|
|
20
|
-
/* @__PURE__ */ e.jsxs(U, { className: "p-4", children: [
|
|
21
|
-
/* @__PURE__ */ e.jsx(V, { className: "text-sm font-medium font-mono tracking-tight text-foreground group-hover:text-primary transition-colors", children: s.title }),
|
|
22
|
-
s.description && /* @__PURE__ */ e.jsx(W, { className: "text-xs text-muted-foreground font-mono", children: s.description })
|
|
23
|
-
] }),
|
|
24
|
-
s.badges && s.badges.length > 0 && /* @__PURE__ */ e.jsx(X, { className: "p-4 pt-0", children: /* @__PURE__ */ e.jsx("div", { className: "flex flex-wrap gap-1", children: s.badges.map((S, y) => /* @__PURE__ */ e.jsx(B, { variant: S.variant || "default", className: "text-xs", children: S.label }, y)) }) })
|
|
25
|
-
] }) });
|
|
26
|
-
}
|
|
27
|
-
function Y({
|
|
28
|
-
column: s,
|
|
29
|
-
cards: f
|
|
30
|
-
}) {
|
|
31
|
-
const d = f || [], { setNodeRef: u } = k({
|
|
32
|
-
id: s.id,
|
|
33
|
-
data: {
|
|
34
|
-
type: "column"
|
|
35
|
-
}
|
|
36
|
-
}), p = s.limit && d.length >= s.limit;
|
|
37
|
-
return /* @__PURE__ */ e.jsxs(
|
|
38
|
-
"div",
|
|
39
|
-
{
|
|
40
|
-
ref: u,
|
|
41
|
-
className: E(
|
|
42
|
-
"flex flex-col w-80 flex-shrink-0 rounded-lg border border-border bg-card/20 backdrop-blur-sm shadow-xl",
|
|
43
|
-
s.className
|
|
44
|
-
),
|
|
45
|
-
children: [
|
|
46
|
-
/* @__PURE__ */ e.jsx("div", { className: "p-4 border-b border-border/50 bg-muted/20", children: /* @__PURE__ */ e.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
47
|
-
/* @__PURE__ */ e.jsx("h3", { className: "font-mono text-sm font-semibold tracking-wider text-primary/90 uppercase", children: s.title }),
|
|
48
|
-
/* @__PURE__ */ e.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
49
|
-
/* @__PURE__ */ e.jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: [
|
|
50
|
-
d.length,
|
|
51
|
-
s.limit && ` / ${s.limit}`
|
|
52
|
-
] }),
|
|
53
|
-
p && /* @__PURE__ */ e.jsx(B, { variant: "destructive", className: "text-xs", children: "Full" })
|
|
54
|
-
] })
|
|
55
|
-
] }) }),
|
|
56
|
-
/* @__PURE__ */ e.jsx(J, { className: "flex-1 p-4", children: /* @__PURE__ */ e.jsx(
|
|
57
|
-
q,
|
|
58
|
-
{
|
|
59
|
-
items: d.map((t) => t.id),
|
|
60
|
-
strategy: z,
|
|
61
|
-
children: /* @__PURE__ */ e.jsx("div", { className: "space-y-2", children: d.map((t) => /* @__PURE__ */ e.jsx(R, { card: t }, t.id)) })
|
|
62
|
-
}
|
|
63
|
-
) })
|
|
64
|
-
]
|
|
65
|
-
}
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
function se({ columns: s, onCardMove: f, className: d }) {
|
|
69
|
-
const [u, p] = x.useState(null), t = x.useMemo(() => (s || []).map((r) => ({
|
|
70
|
-
...r,
|
|
71
|
-
cards: r.cards || []
|
|
72
|
-
})), [s]), [n, b] = x.useState(t);
|
|
73
|
-
x.useEffect(() => {
|
|
74
|
-
b(t);
|
|
75
|
-
}, [t]);
|
|
76
|
-
const S = L(
|
|
77
|
-
P($, {
|
|
78
|
-
activationConstraint: {
|
|
79
|
-
distance: 8
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
), y = (r) => {
|
|
83
|
-
const { active: a } = r, i = A(a.id);
|
|
84
|
-
p(i);
|
|
85
|
-
}, O = (r) => {
|
|
86
|
-
const { active: a, over: i } = r;
|
|
87
|
-
if (p(null), !i) return;
|
|
88
|
-
const c = a.id, g = i.id;
|
|
89
|
-
if (c === g) return;
|
|
90
|
-
const l = I(c), h = I(g) || K(g);
|
|
91
|
-
if (!(!l || !h))
|
|
92
|
-
if (l.id === h.id) {
|
|
93
|
-
const m = [...l.cards], v = m.findIndex((o) => o.id === c), D = m.findIndex((o) => o.id === g), w = M(m, v, D);
|
|
94
|
-
b(
|
|
95
|
-
(o) => o.map(
|
|
96
|
-
(C) => C.id === l.id ? { ...C, cards: w } : C
|
|
97
|
-
)
|
|
98
|
-
);
|
|
99
|
-
} else {
|
|
100
|
-
const m = [...l.cards], v = [...h.cards], D = m.findIndex((j) => j.id === c), o = g === h.id ? v.length : v.findIndex((j) => j.id === g), [C] = m.splice(D, 1);
|
|
101
|
-
v.splice(o, 0, C), b(
|
|
102
|
-
(j) => j.map((N) => N.id === l.id ? { ...N, cards: m } : N.id === h.id ? { ...N, cards: v } : N)
|
|
103
|
-
), f && f(c, l.id, h.id, o);
|
|
104
|
-
}
|
|
105
|
-
}, A = x.useCallback(
|
|
106
|
-
(r) => {
|
|
107
|
-
for (const a of n) {
|
|
108
|
-
const i = a.cards.find((c) => c.id === r);
|
|
109
|
-
if (i) return i;
|
|
110
|
-
}
|
|
111
|
-
return null;
|
|
112
|
-
},
|
|
113
|
-
[n]
|
|
114
|
-
), I = x.useCallback(
|
|
115
|
-
(r) => n.find((a) => a.cards.some((i) => i.id === r)) || null,
|
|
116
|
-
[n]
|
|
117
|
-
), K = x.useCallback(
|
|
118
|
-
(r) => n.find((a) => a.id === r) || null,
|
|
119
|
-
[n]
|
|
120
|
-
);
|
|
121
|
-
return /* @__PURE__ */ e.jsxs(
|
|
122
|
-
T,
|
|
123
|
-
{
|
|
124
|
-
sensors: S,
|
|
125
|
-
collisionDetection: F,
|
|
126
|
-
onDragStart: y,
|
|
127
|
-
onDragEnd: O,
|
|
128
|
-
children: [
|
|
129
|
-
/* @__PURE__ */ e.jsx("div", { className: E("flex gap-4 overflow-x-auto p-4", d), children: n.map((r) => /* @__PURE__ */ e.jsx(
|
|
130
|
-
Y,
|
|
131
|
-
{
|
|
132
|
-
column: r,
|
|
133
|
-
cards: r.cards
|
|
134
|
-
},
|
|
135
|
-
r.id
|
|
136
|
-
)) }),
|
|
137
|
-
/* @__PURE__ */ e.jsx(H, { children: u ? /* @__PURE__ */ e.jsx(R, { card: u }) : null })
|
|
138
|
-
]
|
|
139
|
-
}
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
export {
|
|
143
|
-
se as default
|
|
144
|
-
};
|