@ablogcms/kanban 3.2.28-beta.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/dist/index.mjs ADDED
@@ -0,0 +1,981 @@
1
+ // src/components/Board.tsx
2
+ import { useMemo as useMemo3, useRef as useRef4 } from "react";
3
+ import { v4 as uuidv43 } from "uuid";
4
+
5
+ // src/context/BoardContext.tsx
6
+ import { createContext, useContext, useReducer } from "react";
7
+
8
+ // src/helpers/LaneHelper.ts
9
+ import { v4 as uuidv4 } from "uuid";
10
+ var LaneHelper = {
11
+ initialiseLanes: (state, { lanes }) => {
12
+ const newLanes = lanes.map((lane) => ({
13
+ ...lane,
14
+ currentPage: 1,
15
+ cards: (lane.cards || []).map((c) => ({ ...c, laneId: lane.id }))
16
+ }));
17
+ return { ...state, lanes: newLanes };
18
+ },
19
+ paginateLane: (state, { laneId, newCards, nextPage }) => {
20
+ const updatedLanes = LaneHelper.appendCardsToLane(state, { laneId, newCards });
21
+ return {
22
+ ...state,
23
+ lanes: updatedLanes.map((lane) => lane.id === laneId ? { ...lane, currentPage: nextPage } : lane)
24
+ };
25
+ },
26
+ appendCardsToLane: (state, { laneId, newCards, index }) => {
27
+ const lane = state.lanes.find((l) => l.id === laneId);
28
+ if (!lane) return state.lanes;
29
+ const existingIds = new Set(lane.cards.map((c) => c.id));
30
+ const cardsWithLaneId = newCards.map((c) => ({ ...c, laneId })).filter((c) => !existingIds.has(c.id));
31
+ return state.lanes.map((l) => {
32
+ if (l.id !== laneId) return l;
33
+ if (index !== void 0) {
34
+ const cards = [...l.cards];
35
+ cards.splice(index, 0, ...cardsWithLaneId);
36
+ return { ...l, cards };
37
+ }
38
+ return { ...l, cards: [...l.cards, ...cardsWithLaneId] };
39
+ });
40
+ },
41
+ appendCardToLane: (state, { laneId, card, index }) => {
42
+ const newLanes = LaneHelper.appendCardsToLane(state, { laneId, newCards: [card], index });
43
+ return { ...state, lanes: newLanes };
44
+ },
45
+ addLane: (state, lane) => {
46
+ const newLane = { id: uuidv4(), cards: [], ...lane };
47
+ return { ...state, lanes: [...state.lanes, newLane] };
48
+ },
49
+ removeCardFromLane: (state, { laneId, cardId }) => {
50
+ const lanes = state.lanes.map((lane) => {
51
+ if (lane.id !== laneId) return lane;
52
+ return { ...lane, cards: lane.cards.filter((card) => card.id !== cardId) };
53
+ });
54
+ return { ...state, lanes };
55
+ },
56
+ moveCardAcrossLanes: (state, { fromLaneId: fromLaneId2, toLaneId: toLaneId2, cardId, index }) => {
57
+ let cardToMove = null;
58
+ const interimLanes = state.lanes.map((lane) => {
59
+ if (lane.id !== fromLaneId2) return lane;
60
+ cardToMove = lane.cards.find((card) => card.id === cardId) || null;
61
+ return { ...lane, cards: lane.cards.filter((card) => card.id !== cardId) };
62
+ });
63
+ if (!cardToMove) return state;
64
+ const interimState = { ...state, lanes: interimLanes };
65
+ return LaneHelper.appendCardToLane(interimState, { laneId: toLaneId2, card: cardToMove, index });
66
+ },
67
+ updateCardsForLane: (state, { laneId, cards }) => {
68
+ const lanes = state.lanes.map((lane) => {
69
+ if (lane.id !== laneId) return lane;
70
+ return { ...lane, cards };
71
+ });
72
+ return { ...state, lanes };
73
+ },
74
+ updateLanes: (state, lanes) => {
75
+ return { ...state, lanes };
76
+ },
77
+ moveLane: (state, { oldIndex, newIndex }) => {
78
+ const lanes = [...state.lanes];
79
+ const [laneToMove] = lanes.splice(oldIndex, 1);
80
+ lanes.splice(newIndex, 0, laneToMove);
81
+ return { ...state, lanes };
82
+ }
83
+ };
84
+ var LaneHelper_default = LaneHelper;
85
+
86
+ // src/context/BoardReducer.ts
87
+ var initialState = { lanes: [] };
88
+ function boardReducer(state, action) {
89
+ switch (action.type) {
90
+ case "LOAD_BOARD":
91
+ return LaneHelper_default.initialiseLanes(state, action.payload);
92
+ case "ADD_CARD":
93
+ return LaneHelper_default.appendCardToLane(state, action.payload);
94
+ case "REMOVE_CARD":
95
+ return LaneHelper_default.removeCardFromLane(state, action.payload);
96
+ case "MOVE_CARD":
97
+ return LaneHelper_default.moveCardAcrossLanes(state, action.payload);
98
+ case "UPDATE_CARDS":
99
+ return LaneHelper_default.updateCardsForLane(state, action.payload);
100
+ case "UPDATE_LANES":
101
+ return LaneHelper_default.updateLanes(state, action.payload);
102
+ case "PAGINATE_LANE":
103
+ return LaneHelper_default.paginateLane(state, action.payload);
104
+ case "MOVE_LANE":
105
+ return LaneHelper_default.moveLane(state, action.payload);
106
+ case "ADD_LANE":
107
+ return LaneHelper_default.addLane(state, action.payload);
108
+ default:
109
+ return state;
110
+ }
111
+ }
112
+
113
+ // src/context/BoardContext.tsx
114
+ import { jsx } from "react/jsx-runtime";
115
+ var BoardStateContext = createContext(null);
116
+ var BoardDispatchContext = createContext(null);
117
+ function BoardProvider({ initialData, children }) {
118
+ const [state, dispatch] = useReducer(
119
+ boardReducer,
120
+ initialData,
121
+ (data) => boardReducer(initialState, { type: "LOAD_BOARD", payload: data })
122
+ );
123
+ return /* @__PURE__ */ jsx(BoardStateContext.Provider, { value: state, children: /* @__PURE__ */ jsx(BoardDispatchContext.Provider, { value: dispatch, children }) });
124
+ }
125
+ function useBoardState() {
126
+ const ctx = useContext(BoardStateContext);
127
+ if (!ctx) throw new Error("useBoardState must be used within BoardProvider");
128
+ return ctx;
129
+ }
130
+ function useBoardDispatch() {
131
+ const ctx = useContext(BoardDispatchContext);
132
+ if (!ctx) throw new Error("useBoardDispatch must be used within BoardProvider");
133
+ return ctx;
134
+ }
135
+
136
+ // src/components/BoardContainer.tsx
137
+ import { useState as useState2, useEffect as useEffect2, useRef as useRef2, useCallback as useCallback2, useMemo as useMemo2 } from "react";
138
+ import {
139
+ DndContext,
140
+ DragOverlay,
141
+ PointerSensor,
142
+ KeyboardSensor,
143
+ useSensor,
144
+ useSensors,
145
+ closestCorners
146
+ } from "@dnd-kit/core";
147
+ import {
148
+ SortableContext as SortableContext2,
149
+ horizontalListSortingStrategy,
150
+ sortableKeyboardCoordinates,
151
+ arrayMove
152
+ } from "@dnd-kit/sortable";
153
+
154
+ // src/components/SortableLane.tsx
155
+ import { useSortable as useSortable2 } from "@dnd-kit/sortable";
156
+ import { CSS as CSS2 } from "@dnd-kit/utilities";
157
+
158
+ // src/components/Lane.tsx
159
+ import { useState, useEffect, useRef, useCallback, useMemo } from "react";
160
+ import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
161
+ import { useDroppable } from "@dnd-kit/core";
162
+ import { v4 as uuidv42 } from "uuid";
163
+ import Spinner from "@ablogcms/components/spinner";
164
+
165
+ // src/components/Card.tsx
166
+ import { CSS } from "@dnd-kit/utilities";
167
+ import { useSortable } from "@dnd-kit/sortable";
168
+
169
+ // src/components/widgets/DeleteButton.tsx
170
+ import { jsx as jsx2 } from "react/jsx-runtime";
171
+ function DeleteButton(props) {
172
+ return /* @__PURE__ */ jsx2("div", { className: "acms-admin-kanban-delete-wrapper", children: /* @__PURE__ */ jsx2("button", { type: "button", className: "acms-admin-kanban-delete-button", onClick: props.onClick, children: "x" }) });
173
+ }
174
+ var DeleteButton_default = DeleteButton;
175
+
176
+ // src/utils/dndIds.ts
177
+ var toLaneId = (id) => `lane:${id}`;
178
+ var toCardId = (id) => `card:${id}`;
179
+ var toDropLaneId = (id) => `droplane:${id}`;
180
+ var fromLaneId = (id) => id.replace(/^lane:/, "");
181
+ var fromCardId = (id) => id.replace(/^card:/, "");
182
+ var fromDropLaneId = (id) => id.replace(/^droplane:/, "");
183
+ var isLaneId = (id) => id.startsWith("lane:");
184
+ var isCardId = (id) => id.startsWith("card:");
185
+ var isDropLaneId = (id) => id.startsWith("droplane:");
186
+
187
+ // src/components/Card.tsx
188
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
189
+ function Card({
190
+ card,
191
+ laneId,
192
+ editable = false,
193
+ hideCardDeleteIcon = false,
194
+ customCardLayout = false,
195
+ components: components2,
196
+ tagStyle,
197
+ cardStyle = {},
198
+ draggable = false,
199
+ cardDraggable = true,
200
+ onCardClick,
201
+ onCardDelete = () => {
202
+ },
203
+ onRemoveCard
204
+ }) {
205
+ const isDraggingEnabled = draggable && cardDraggable;
206
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
207
+ id: toCardId(card.id),
208
+ disabled: !isDraggingEnabled
209
+ });
210
+ const style = {
211
+ transform: CSS.Transform.toString(transform),
212
+ transition,
213
+ opacity: isDragging ? 0 : 1
214
+ };
215
+ const handleRemoveCard = (e) => {
216
+ if (onRemoveCard) onRemoveCard(laneId, card.id);
217
+ onCardDelete(card.id, laneId);
218
+ e.stopPropagation();
219
+ };
220
+ const handleClick = (e) => {
221
+ if (onCardClick) onCardClick(card.id, card.metadata, laneId);
222
+ e.stopPropagation();
223
+ };
224
+ const handleDelete = () => {
225
+ if (onRemoveCard) onRemoveCard(laneId, card.id);
226
+ onCardDelete(card.id, laneId);
227
+ };
228
+ const wrapperStyle = customCardLayout ? { ...cardStyle, padding: 0 } : cardStyle;
229
+ return (
230
+ // ドラッグ有効時は useSortable の attributes/listeners が role/tabIndex/キーボード操作を提供する。
231
+ // 無効時のクリックはカード選択という補助的な操作のため、キーボード対応は必須にしていない(既存挙動を維持)。
232
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
233
+ /* @__PURE__ */ jsxs(
234
+ "article",
235
+ {
236
+ ref: setNodeRef,
237
+ className: "acms-admin-kanban-card",
238
+ "data-id": card.id,
239
+ style: { ...wrapperStyle, ...style },
240
+ onClick: handleClick,
241
+ ...isDraggingEnabled ? { ...attributes, ...listeners } : {},
242
+ children: [
243
+ /* @__PURE__ */ jsx3(components2.Card, { card, laneId, tagStyle, onDelete: handleDelete }),
244
+ editable && !hideCardDeleteIcon && /* @__PURE__ */ jsx3(DeleteButton_default, { onClick: handleRemoveCard })
245
+ ]
246
+ }
247
+ )
248
+ );
249
+ }
250
+ var Card_default = Card;
251
+
252
+ // src/components/Lane.tsx
253
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
254
+ function Lane({
255
+ lane,
256
+ boardId: _boardId,
257
+ // eslint-disable-line @typescript-eslint/no-unused-vars
258
+ index: _index,
259
+ // eslint-disable-line @typescript-eslint/no-unused-vars
260
+ draggable = false,
261
+ cardDraggable = true,
262
+ collapsibleLanes = false,
263
+ editable = false,
264
+ hideCardDeleteIcon = false,
265
+ customCardLayout = false,
266
+ components: components2,
267
+ tagStyle,
268
+ cardStyle,
269
+ titleStyle,
270
+ labelStyle,
271
+ addCardTitle = "Add Card",
272
+ addCardLink,
273
+ onLaneScroll,
274
+ onCardClick,
275
+ onCardDelete = () => {
276
+ },
277
+ onCardAdd = () => {
278
+ },
279
+ onLaneClick,
280
+ laneSortFunction,
281
+ dragHandleListeners,
282
+ dragHandleAttributes
283
+ }) {
284
+ const dispatch = useBoardDispatch();
285
+ const [loading, setLoading] = useState(false);
286
+ const [addCardMode, setAddCardMode] = useState(false);
287
+ const [collapsed, setCollapsed] = useState(false);
288
+ const scrollRef = useRef(null);
289
+ const { id, cards = [], style, droppable, currentPage = 1 } = lane;
290
+ const { isOver, setNodeRef: setDropRef } = useDroppable({
291
+ id: toDropLaneId(id),
292
+ disabled: droppable === false
293
+ });
294
+ const handleScroll = useCallback(
295
+ (evt) => {
296
+ const node = evt.target;
297
+ const elemScrollPosition = node.scrollHeight - node.scrollTop - node.clientHeight;
298
+ if (elemScrollPosition <= 0 && onLaneScroll && !loading) {
299
+ setLoading(true);
300
+ const nextPage = currentPage + 1;
301
+ onLaneScroll(nextPage, id).then((moreCards) => {
302
+ if (!moreCards || moreCards.length === 0) {
303
+ node.scrollTop -= 100;
304
+ } else {
305
+ dispatch({ type: "PAGINATE_LANE", payload: { laneId: id, newCards: moreCards, nextPage } });
306
+ }
307
+ setLoading(false);
308
+ });
309
+ }
310
+ },
311
+ [currentPage, id, loading, onLaneScroll, dispatch]
312
+ );
313
+ useEffect(() => {
314
+ const node = scrollRef.current;
315
+ if (node) {
316
+ node.addEventListener("scroll", handleScroll);
317
+ return () => node.removeEventListener("scroll", handleScroll);
318
+ }
319
+ }, [handleScroll]);
320
+ const removeCard = (laneId, cardId) => {
321
+ dispatch({ type: "REMOVE_CARD", payload: { laneId, cardId } });
322
+ };
323
+ const addNewCard = (params) => {
324
+ const newCardId = uuidv42();
325
+ setAddCardMode(false);
326
+ const card = { id: newCardId, laneId: id, ...params };
327
+ dispatch({ type: "ADD_CARD", payload: { laneId: id, card } });
328
+ onCardAdd(card, id);
329
+ };
330
+ const toggleLaneCollapsed = () => {
331
+ if (collapsibleLanes) setCollapsed((c) => !c);
332
+ };
333
+ const renderAddCardLink = () => {
334
+ if (addCardLink) {
335
+ return /* @__PURE__ */ jsx4(
336
+ "span",
337
+ {
338
+ role: "button",
339
+ tabIndex: 0,
340
+ onClick: () => setAddCardMode(true),
341
+ onKeyDown: (e) => {
342
+ if (e.key === "Enter" || e.key === " ") {
343
+ e.preventDefault();
344
+ setAddCardMode(true);
345
+ }
346
+ },
347
+ children: addCardLink
348
+ }
349
+ );
350
+ }
351
+ return /* @__PURE__ */ jsx4("button", { type: "button", className: "acms-admin-kanban-add-card-link", onClick: () => setAddCardMode(true), children: addCardTitle });
352
+ };
353
+ const renderFooter = () => {
354
+ if (collapsibleLanes && cards.length > 0) {
355
+ return /* @__PURE__ */ jsx4(
356
+ "button",
357
+ {
358
+ type: "button",
359
+ className: "acms-admin-kanban-lane-footer",
360
+ onClick: toggleLaneCollapsed,
361
+ "aria-label": collapsed ? "Expand" : "Collapse",
362
+ children: /* @__PURE__ */ jsx4("span", { className: collapsed ? "acms-admin-kanban-expand-btn" : "acms-admin-kanban-collapse-btn" })
363
+ }
364
+ );
365
+ }
366
+ return null;
367
+ };
368
+ const sortedCards = useMemo(() => {
369
+ const showableCards = collapsed ? [] : cards;
370
+ if (!laneSortFunction) return showableCards;
371
+ return [...showableCards].sort(laneSortFunction);
372
+ }, [cards, collapsed, laneSortFunction]);
373
+ const cardIds = useMemo(() => sortedCards.map((c) => toCardId(c.id)), [sortedCards]);
374
+ return (
375
+ // レーン全体のクリックは任意コールバック用の補助的なフックであり、内部にカード/ボタン等の
376
+ // インタラクティブ要素を含むため要素自体をボタン化しない(既存挙動を維持)。
377
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
378
+ /* @__PURE__ */ jsxs2(
379
+ "section",
380
+ {
381
+ style,
382
+ onClick: () => onLaneClick && onLaneClick(id),
383
+ draggable: false,
384
+ className: "acms-admin-kanban-lane",
385
+ children: [
386
+ /* @__PURE__ */ jsx4(
387
+ components2.LaneHeader,
388
+ {
389
+ lane,
390
+ titleStyle,
391
+ labelStyle,
392
+ dragHandleListeners,
393
+ dragHandleAttributes,
394
+ onToggleCollapsed: toggleLaneCollapsed
395
+ }
396
+ ),
397
+ /* @__PURE__ */ jsxs2(
398
+ "div",
399
+ {
400
+ ref: (node) => {
401
+ scrollRef.current = node;
402
+ setDropRef(node);
403
+ },
404
+ className: `acms-admin-kanban-lane-scroll${isOver ? " acms-admin-kanban-lane-scroll-dragover" : ""}`,
405
+ children: [
406
+ /* @__PURE__ */ jsx4(SortableContext, { items: cardIds, strategy: verticalListSortingStrategy, children: sortedCards.map((card) => /* @__PURE__ */ jsx4(
407
+ Card_default,
408
+ {
409
+ card,
410
+ laneId: id,
411
+ draggable,
412
+ cardDraggable,
413
+ editable,
414
+ hideCardDeleteIcon,
415
+ customCardLayout,
416
+ components: components2,
417
+ tagStyle,
418
+ cardStyle,
419
+ onCardClick,
420
+ onCardDelete,
421
+ onRemoveCard: removeCard
422
+ },
423
+ card.id
424
+ )) }),
425
+ editable && !addCardMode && renderAddCardLink(),
426
+ addCardMode && /* @__PURE__ */ jsx4("span", { children: /* @__PURE__ */ jsx4(components2.NewCard, { laneId: id, onCancel: () => setAddCardMode(false), onAdd: addNewCard }) })
427
+ ]
428
+ }
429
+ ),
430
+ loading && /* @__PURE__ */ jsx4(Spinner, { size: 24, style: { display: "block", margin: "15px auto" } }),
431
+ renderFooter()
432
+ ]
433
+ },
434
+ id
435
+ )
436
+ );
437
+ }
438
+ var Lane_default = Lane;
439
+
440
+ // src/components/SortableLane.tsx
441
+ import { jsx as jsx5 } from "react/jsx-runtime";
442
+ function SortableLane({ lane, disabled = false, ...laneProps }) {
443
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable2({
444
+ id: toLaneId(lane.id),
445
+ disabled,
446
+ data: { type: "LANE", lane }
447
+ });
448
+ const style = {
449
+ transform: CSS2.Transform.toString(transform),
450
+ transition,
451
+ opacity: isDragging ? 0.3 : 1,
452
+ display: "inline-block"
453
+ };
454
+ return /* @__PURE__ */ jsx5("div", { ref: setNodeRef, style, children: /* @__PURE__ */ jsx5(
455
+ Lane_default,
456
+ {
457
+ lane,
458
+ dragHandleListeners: listeners,
459
+ dragHandleAttributes: attributes,
460
+ ...laneProps
461
+ }
462
+ ) });
463
+ }
464
+ var SortableLane_default = SortableLane;
465
+
466
+ // src/components/DragOverlay/CardOverlay.tsx
467
+ import { jsx as jsx6 } from "react/jsx-runtime";
468
+ var overlayStyle = {
469
+ transform: "rotate(3deg)",
470
+ boxShadow: "0 5px 15px rgba(0,0,0,0.3)",
471
+ cursor: "grabbing"
472
+ };
473
+ function CardOverlay({ card, components: components2, tagStyle, cardStyle = {}, customCardLayout = false }) {
474
+ const wrapperStyle = customCardLayout ? { ...cardStyle, padding: 0, ...overlayStyle } : { ...cardStyle, ...overlayStyle };
475
+ return /* @__PURE__ */ jsx6("article", { className: "acms-admin-kanban-card", "data-id": card.id, style: wrapperStyle, children: /* @__PURE__ */ jsx6(components2.Card, { card, laneId: card.laneId, tagStyle, onDelete: () => {
476
+ } }) });
477
+ }
478
+ var CardOverlay_default = CardOverlay;
479
+
480
+ // src/components/DragOverlay/LaneOverlay.tsx
481
+ import { jsx as jsx7 } from "react/jsx-runtime";
482
+ var overlayStyle2 = {
483
+ transform: "rotate(3deg)",
484
+ boxShadow: "0 5px 15px rgba(0,0,0,0.3)",
485
+ cursor: "grabbing",
486
+ opacity: 0.9
487
+ };
488
+ function LaneOverlay({ lane }) {
489
+ return /* @__PURE__ */ jsx7(
490
+ "section",
491
+ {
492
+ style: { ...lane.style || {}, ...overlayStyle2 },
493
+ className: "acms-admin-kanban-lane",
494
+ children: /* @__PURE__ */ jsx7("span", { style: { fontWeight: "bold", padding: "5px" }, children: lane.title })
495
+ }
496
+ );
497
+ }
498
+ var LaneOverlay_default = LaneOverlay;
499
+
500
+ // src/components/BoardContainer.tsx
501
+ import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
502
+ function BoardContainer(props) {
503
+ const {
504
+ data,
505
+ id,
506
+ draggable = false,
507
+ laneDraggable = true,
508
+ style,
509
+ editable = false,
510
+ canAddLanes = false,
511
+ addLaneTitle = "+ Add another lane",
512
+ components: components2,
513
+ onDataChange = () => {
514
+ },
515
+ eventBusHandle,
516
+ handleLaneDragStart = () => {
517
+ },
518
+ handleLaneDragEnd = () => {
519
+ },
520
+ handleDragStart = () => {
521
+ },
522
+ handleDragEnd = () => {
523
+ }
524
+ } = props;
525
+ const state = useBoardState();
526
+ const dispatch = useBoardDispatch();
527
+ const [addLaneMode, setAddLaneMode] = useState2(false);
528
+ const [activeId, setActiveId] = useState2(null);
529
+ const [activeLaneId, setActiveLaneId] = useState2(null);
530
+ const prevStateRef = useRef2(state);
531
+ const prevDataRef = useRef2(data);
532
+ useEffect2(() => {
533
+ if (JSON.stringify(data) !== JSON.stringify(prevDataRef.current)) {
534
+ dispatch({ type: "LOAD_BOARD", payload: data });
535
+ onDataChange(data);
536
+ prevDataRef.current = data;
537
+ }
538
+ }, [data, dispatch, onDataChange]);
539
+ useEffect2(() => {
540
+ if (state !== prevStateRef.current && prevStateRef.current.lanes.length > 0) {
541
+ onDataChange(state);
542
+ prevStateRef.current = state;
543
+ }
544
+ }, [state, onDataChange]);
545
+ useEffect2(() => {
546
+ if (!eventBusHandle) return;
547
+ const eventBus = {
548
+ publish: (event) => {
549
+ switch (event.type) {
550
+ case "ADD_CARD":
551
+ dispatch({ type: "ADD_CARD", payload: { laneId: event.laneId, card: event.card } });
552
+ break;
553
+ case "REMOVE_CARD":
554
+ dispatch({ type: "REMOVE_CARD", payload: { laneId: event.laneId, cardId: event.cardId } });
555
+ break;
556
+ case "REFRESH_BOARD":
557
+ dispatch({ type: "LOAD_BOARD", payload: event.data });
558
+ break;
559
+ case "MOVE_CARD":
560
+ dispatch({
561
+ type: "MOVE_CARD",
562
+ payload: {
563
+ fromLaneId: event.fromLaneId,
564
+ toLaneId: event.toLaneId,
565
+ cardId: event.cardId,
566
+ index: event.index
567
+ }
568
+ });
569
+ break;
570
+ case "UPDATE_LANES":
571
+ dispatch({ type: "UPDATE_LANES", payload: event.lanes });
572
+ break;
573
+ default:
574
+ break;
575
+ }
576
+ }
577
+ };
578
+ eventBusHandle(eventBus);
579
+ }, [eventBusHandle, dispatch]);
580
+ const sensors = useSensors(
581
+ useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
582
+ useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
583
+ );
584
+ const findCardLane = useCallback2(
585
+ (cardId) => {
586
+ return state.lanes.find((lane) => lane.cards.some((c) => c.id === cardId));
587
+ },
588
+ [state.lanes]
589
+ );
590
+ const handleDragStartEvent = (event) => {
591
+ const { active } = event;
592
+ setActiveId(active.id);
593
+ if (isCardId(active.id)) {
594
+ const cardId = fromCardId(active.id);
595
+ const lane = findCardLane(cardId);
596
+ if (lane) {
597
+ setActiveLaneId(lane.id);
598
+ handleDragStart(cardId, lane.id);
599
+ }
600
+ } else if (isLaneId(active.id)) {
601
+ const laneId = fromLaneId(active.id);
602
+ handleLaneDragStart(laneId);
603
+ }
604
+ };
605
+ const handleDragOver = useCallback2(
606
+ (event) => {
607
+ const { active, over } = event;
608
+ if (!over || !isCardId(active.id)) return;
609
+ if (active.id === over.id) return;
610
+ const activeCardId = fromCardId(active.id);
611
+ const overId = over.id;
612
+ let overLaneId;
613
+ let overIndex;
614
+ if (isLaneId(overId) || isDropLaneId(overId)) {
615
+ overLaneId = isLaneId(overId) ? fromLaneId(overId) : fromDropLaneId(overId);
616
+ const overLane = state.lanes.find((l) => l.id === overLaneId);
617
+ overIndex = overLane?.cards.length ?? 0;
618
+ } else if (isCardId(overId)) {
619
+ const overCardId = fromCardId(overId);
620
+ const overLane = state.lanes.find((l) => l.cards.some((c) => c.id === overCardId));
621
+ if (!overLane) return;
622
+ overLaneId = overLane.id;
623
+ overIndex = overLane.cards.findIndex((c) => c.id === overCardId);
624
+ } else {
625
+ return;
626
+ }
627
+ const currentLaneId = activeLaneId || findCardLane(activeCardId)?.id;
628
+ if (!currentLaneId || currentLaneId === overLaneId) return;
629
+ const targetLane = state.lanes.find((l) => l.id === overLaneId);
630
+ if (targetLane?.droppable === false) return;
631
+ dispatch({
632
+ type: "MOVE_CARD",
633
+ payload: { fromLaneId: currentLaneId, toLaneId: overLaneId, cardId: activeCardId, index: overIndex }
634
+ });
635
+ setActiveLaneId(overLaneId);
636
+ },
637
+ [state.lanes, activeLaneId, findCardLane, dispatch]
638
+ );
639
+ const handleDragEndEvent = useCallback2(
640
+ (event) => {
641
+ const { active, over } = event;
642
+ if (over && isCardId(active.id)) {
643
+ const activeCardId = fromCardId(active.id);
644
+ if (isCardId(over.id)) {
645
+ const overCardId = fromCardId(over.id);
646
+ if (activeCardId !== overCardId) {
647
+ const lane = state.lanes.find((l) => l.cards.some((c) => c.id === activeCardId));
648
+ if (lane) {
649
+ const oldIndex = lane.cards.findIndex((c) => c.id === activeCardId);
650
+ const newIndex = lane.cards.findIndex((c) => c.id === overCardId);
651
+ if (oldIndex !== -1 && newIndex !== -1 && lane.cards[oldIndex]?.laneId === lane.cards[newIndex]?.laneId) {
652
+ const newCards = arrayMove(lane.cards, oldIndex, newIndex);
653
+ dispatch({ type: "UPDATE_CARDS", payload: { laneId: lane.id, cards: newCards } });
654
+ handleDragEnd(activeCardId, lane.id, lane.id, newIndex, lane.cards[oldIndex]);
655
+ }
656
+ }
657
+ }
658
+ }
659
+ if (activeLaneId) {
660
+ const finalLane = state.lanes.find((l) => l.id === activeLaneId);
661
+ const cardDetails = finalLane?.cards.find((c) => c.id === activeCardId);
662
+ const originalLane = findCardLane(activeCardId);
663
+ if (cardDetails && originalLane && originalLane.id !== activeLaneId) {
664
+ const position = finalLane?.cards.findIndex((c) => c.id === activeCardId) ?? 0;
665
+ handleDragEnd(activeCardId, originalLane.id, activeLaneId, position, cardDetails);
666
+ }
667
+ }
668
+ } else if (over && isLaneId(active.id)) {
669
+ const activeLaneIdStr = fromLaneId(active.id);
670
+ const overLaneIdStr = isLaneId(over.id) ? fromLaneId(over.id) : null;
671
+ if (overLaneIdStr && activeLaneIdStr !== overLaneIdStr) {
672
+ const oldIndex = state.lanes.findIndex((l) => l.id === activeLaneIdStr);
673
+ const newIndex = state.lanes.findIndex((l) => l.id === overLaneIdStr);
674
+ if (oldIndex !== -1 && newIndex !== -1) {
675
+ dispatch({ type: "MOVE_LANE", payload: { oldIndex, newIndex } });
676
+ const payload = state.lanes[oldIndex];
677
+ handleLaneDragEnd(activeLaneIdStr, newIndex, payload);
678
+ }
679
+ }
680
+ }
681
+ setActiveId(null);
682
+ setActiveLaneId(null);
683
+ },
684
+ [state.lanes, activeLaneId, findCardLane, dispatch, handleDragEnd, handleLaneDragEnd]
685
+ );
686
+ const activeCard = useMemo2(() => {
687
+ if (!activeId || !isCardId(activeId)) return null;
688
+ const cardId = fromCardId(activeId);
689
+ const lane = findCardLane(cardId);
690
+ return lane?.cards.find((c) => c.id === cardId) ?? null;
691
+ }, [activeId, findCardLane]);
692
+ const activeLane = useMemo2(() => {
693
+ if (!activeId || !isLaneId(activeId)) return null;
694
+ return state.lanes.find((l) => l.id === fromLaneId(activeId)) ?? null;
695
+ }, [activeId, state.lanes]);
696
+ const laneIds = useMemo2(() => state.lanes.map((l) => toLaneId(l.id)), [state.lanes]);
697
+ const {
698
+ onLaneScroll,
699
+ onCardClick,
700
+ onCardDelete,
701
+ onCardAdd,
702
+ onLaneClick,
703
+ addCardLink,
704
+ laneSortFunction,
705
+ cardDraggable,
706
+ collapsibleLanes,
707
+ hideCardDeleteIcon,
708
+ customCardLayout,
709
+ tagStyle,
710
+ cardDragClass,
711
+ addCardTitle
712
+ } = props;
713
+ const passthroughProps = {
714
+ onLaneScroll,
715
+ onCardClick,
716
+ onCardDelete,
717
+ onCardAdd,
718
+ onLaneClick,
719
+ addCardLink,
720
+ laneSortFunction,
721
+ draggable,
722
+ cardDraggable,
723
+ collapsibleLanes,
724
+ editable,
725
+ canAddLanes,
726
+ hideCardDeleteIcon,
727
+ customCardLayout,
728
+ components: components2,
729
+ tagStyle,
730
+ handleDragStart,
731
+ handleDragEnd,
732
+ cardDragClass,
733
+ addCardTitle
734
+ };
735
+ const addNewLane = (params) => {
736
+ setAddLaneMode(false);
737
+ dispatch({ type: "ADD_LANE", payload: params });
738
+ };
739
+ const renderNewLane = () => /* @__PURE__ */ jsx8("span", { children: /* @__PURE__ */ jsx8(components2.NewLane, { onCancel: () => setAddLaneMode(false), onAdd: addNewLane }) });
740
+ const renderDragOverlayContent = () => {
741
+ if (activeCard) {
742
+ return /* @__PURE__ */ jsx8(
743
+ CardOverlay_default,
744
+ {
745
+ card: activeCard,
746
+ components: components2,
747
+ tagStyle: passthroughProps.tagStyle,
748
+ customCardLayout: passthroughProps.customCardLayout
749
+ }
750
+ );
751
+ }
752
+ if (activeLane) {
753
+ return /* @__PURE__ */ jsx8(LaneOverlay_default, { lane: activeLane });
754
+ }
755
+ return null;
756
+ };
757
+ return /* @__PURE__ */ jsxs3(
758
+ DndContext,
759
+ {
760
+ sensors,
761
+ collisionDetection: closestCorners,
762
+ onDragStart: handleDragStartEvent,
763
+ onDragOver: handleDragOver,
764
+ onDragEnd: handleDragEndEvent,
765
+ children: [
766
+ /* @__PURE__ */ jsxs3("div", { style, draggable: false, className: "acms-admin-kanban-board", "data-boardid": id, children: [
767
+ /* @__PURE__ */ jsx8(SortableContext2, { items: laneIds, strategy: horizontalListSortingStrategy, children: state.lanes.map((lane, index) => {
768
+ const isLaneDraggable = draggable && laneDraggable && (lane.movable === void 0 ? true : lane.movable);
769
+ return /* @__PURE__ */ jsx8(
770
+ SortableLane_default,
771
+ {
772
+ lane,
773
+ boardId: `TrelloBoard${id}`,
774
+ index,
775
+ disabled: !isLaneDraggable,
776
+ laneDraggable,
777
+ ...passthroughProps
778
+ },
779
+ lane.id
780
+ );
781
+ }) }),
782
+ editable && canAddLanes && !addLaneMode && /* @__PURE__ */ jsx8(
783
+ "span",
784
+ {
785
+ role: "button",
786
+ tabIndex: 0,
787
+ style: { cursor: "pointer", padding: "10px", color: "#fff" },
788
+ onClick: () => setAddLaneMode(true),
789
+ onKeyDown: (e) => {
790
+ if (e.key === "Enter" || e.key === " ") {
791
+ e.preventDefault();
792
+ setAddLaneMode(true);
793
+ }
794
+ },
795
+ children: addLaneTitle
796
+ }
797
+ ),
798
+ addLaneMode && renderNewLane()
799
+ ] }),
800
+ /* @__PURE__ */ jsx8(DragOverlay, { children: renderDragOverlayContent() })
801
+ ]
802
+ }
803
+ );
804
+ }
805
+ var BoardContainer_default = BoardContainer;
806
+
807
+ // src/components/Tag.tsx
808
+ import { jsx as jsx9 } from "react/jsx-runtime";
809
+ function Tag({ title, color, bgcolor, tagStyle }) {
810
+ const style = {
811
+ color: color || "white",
812
+ backgroundColor: bgcolor || "orange",
813
+ ...tagStyle
814
+ };
815
+ return /* @__PURE__ */ jsx9("span", { className: "acms-admin-kanban-tag", style, children: title });
816
+ }
817
+ var Tag_default = Tag;
818
+
819
+ // src/components/DefaultCard.tsx
820
+ import { jsx as jsx10, jsxs as jsxs4 } from "react/jsx-runtime";
821
+ function DefaultCard({ card, tagStyle }) {
822
+ const { title, description, label, tags } = card;
823
+ return /* @__PURE__ */ jsxs4("span", { children: [
824
+ /* @__PURE__ */ jsxs4("header", { className: "acms-admin-kanban-card-header", children: [
825
+ /* @__PURE__ */ jsx10("span", { className: "acms-admin-kanban-card-title", children: title }),
826
+ /* @__PURE__ */ jsx10("span", { className: "acms-admin-kanban-card-label", children: label })
827
+ ] }),
828
+ /* @__PURE__ */ jsx10("div", { className: "acms-admin-kanban-card-detail", children: description }),
829
+ tags && /* @__PURE__ */ jsx10("div", { className: "acms-admin-kanban-card-footer", children: tags.map((tag) => /* @__PURE__ */ jsx10(Tag_default, { ...tag, tagStyle }, tag.title)) })
830
+ ] });
831
+ }
832
+ var DefaultCard_default = DefaultCard;
833
+
834
+ // src/components/DefaultLaneHeader.tsx
835
+ import { jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
836
+ function DefaultLaneHeader({
837
+ lane,
838
+ titleStyle,
839
+ labelStyle,
840
+ dragHandleListeners,
841
+ dragHandleAttributes,
842
+ onToggleCollapsed
843
+ }) {
844
+ return /* @__PURE__ */ jsxs5(
845
+ "header",
846
+ {
847
+ className: "acms-admin-kanban-lane-header",
848
+ onDoubleClick: onToggleCollapsed,
849
+ ...dragHandleListeners || {},
850
+ ...dragHandleAttributes || {},
851
+ children: [
852
+ /* @__PURE__ */ jsx11("span", { className: "acms-admin-kanban-lane-title", style: titleStyle, children: lane.title }),
853
+ lane.label && /* @__PURE__ */ jsx11("span", { className: "acms-admin-kanban-lane-label", children: /* @__PURE__ */ jsx11("span", { style: labelStyle, children: lane.label }) })
854
+ ]
855
+ }
856
+ );
857
+ }
858
+ var DefaultLaneHeader_default = DefaultLaneHeader;
859
+
860
+ // src/components/NewCard.tsx
861
+ import { useState as useState4 } from "react";
862
+ import { Button } from "@ablogcms/components/button";
863
+
864
+ // src/components/widgets/EditableLabel.tsx
865
+ import { useRef as useRef3, useState as useState3, useEffect as useEffect3 } from "react";
866
+ import { jsx as jsx12 } from "react/jsx-runtime";
867
+ function EditableLabel({ onChange = () => {
868
+ }, placeholder = "", autoFocus = false }) {
869
+ const [text, setText] = useState3("");
870
+ const divRef = useRef3(null);
871
+ useEffect3(() => {
872
+ if (autoFocus && divRef.current) {
873
+ divRef.current.focus();
874
+ }
875
+ }, [autoFocus]);
876
+ const onTextChange = (ev) => {
877
+ setText(ev.currentTarget.innerText);
878
+ };
879
+ const onBlur = () => {
880
+ onChange(text);
881
+ };
882
+ const onPaste = (ev) => {
883
+ ev.preventDefault();
884
+ const pastedText = ev.clipboardData.getData("text");
885
+ document.execCommand("insertText", false, pastedText);
886
+ };
887
+ const className = `acms-admin-kanban-editable-label${text === "" ? " acms-admin-kanban-editable-label--has-placeholder" : ""}`;
888
+ return /* @__PURE__ */ jsx12(
889
+ "div",
890
+ {
891
+ ref: divRef,
892
+ contentEditable: true,
893
+ className,
894
+ onPaste,
895
+ onBlur,
896
+ onInput: onTextChange,
897
+ "data-placeholder": placeholder,
898
+ suppressContentEditableWarning: true
899
+ }
900
+ );
901
+ }
902
+ var EditableLabel_default = EditableLabel;
903
+
904
+ // src/components/NewCard.tsx
905
+ import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
906
+ function NewCard({
907
+ laneId: _laneId,
908
+ // eslint-disable-line @typescript-eslint/no-unused-vars
909
+ onCancel,
910
+ onAdd
911
+ }) {
912
+ const [formData, setFormData] = useState4({ title: "", label: "", description: "" });
913
+ const updateField = (field, value) => {
914
+ setFormData((prev) => ({ ...prev, [field]: value }));
915
+ };
916
+ const handleAdd = () => {
917
+ onAdd(formData);
918
+ };
919
+ return /* @__PURE__ */ jsxs6("div", { className: "acms-admin-kanban-new-lane", children: [
920
+ /* @__PURE__ */ jsxs6("div", { className: "acms-admin-kanban-card", children: [
921
+ /* @__PURE__ */ jsxs6("header", { className: "acms-admin-kanban-card-header", children: [
922
+ /* @__PURE__ */ jsx13("span", { className: "acms-admin-kanban-card-title", children: /* @__PURE__ */ jsx13(EditableLabel_default, { placeholder: "title", onChange: (val) => updateField("title", val), autoFocus: true }) }),
923
+ /* @__PURE__ */ jsx13("span", { className: "acms-admin-kanban-card-label", children: /* @__PURE__ */ jsx13(EditableLabel_default, { placeholder: "label", onChange: (val) => updateField("label", val) }) })
924
+ ] }),
925
+ /* @__PURE__ */ jsx13("div", { className: "acms-admin-kanban-card-detail", children: /* @__PURE__ */ jsx13(EditableLabel_default, { placeholder: "description", onChange: (val) => updateField("description", val) }) })
926
+ ] }),
927
+ /* @__PURE__ */ jsx13(Button, { type: "button", variant: "success", onClick: handleAdd, children: "Add" }),
928
+ /* @__PURE__ */ jsx13(Button, { type: "button", variant: "text", onClick: onCancel, children: "Cancel" })
929
+ ] });
930
+ }
931
+ var NewCard_default = NewCard;
932
+
933
+ // src/components/NewLane.tsx
934
+ import { useState as useState5 } from "react";
935
+ import { Button as Button2 } from "@ablogcms/components/button";
936
+ import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
937
+ function NewLane({ onCancel, onAdd }) {
938
+ const [formData, setFormData] = useState5({ title: "" });
939
+ const updateField = (field, value) => {
940
+ setFormData((prev) => ({ ...prev, [field]: value }));
941
+ };
942
+ const handleAdd = () => {
943
+ onAdd(formData);
944
+ };
945
+ return /* @__PURE__ */ jsxs7("div", { className: "acms-admin-kanban-new-lane", children: [
946
+ /* @__PURE__ */ jsx14("div", { className: "acms-admin-kanban-new-lane-title", children: /* @__PURE__ */ jsx14(EditableLabel_default, { placeholder: "title", onChange: (val) => updateField("title", val), autoFocus: true }) }),
947
+ /* @__PURE__ */ jsxs7("div", { className: "acms-admin-kanban-new-lane-buttons", children: [
948
+ /* @__PURE__ */ jsx14(Button2, { type: "button", variant: "success", onClick: handleAdd, children: "Add" }),
949
+ /* @__PURE__ */ jsx14(Button2, { type: "button", variant: "text", onClick: onCancel, children: "Cancel" })
950
+ ] })
951
+ ] });
952
+ }
953
+ var NewLane_default = NewLane;
954
+
955
+ // src/components.ts
956
+ var components = {
957
+ Card: DefaultCard_default,
958
+ LaneHeader: DefaultLaneHeader_default,
959
+ NewCard: NewCard_default,
960
+ NewLane: NewLane_default
961
+ };
962
+
963
+ // src/components/Board.tsx
964
+ import { jsx as jsx15 } from "react/jsx-runtime";
965
+ function Board(props) {
966
+ const boardId = useRef4(uuidv43()).current;
967
+ const components2 = useMemo3(() => ({ ...components, ...props.components }), [props.components]);
968
+ return /* @__PURE__ */ jsx15(BoardProvider, { initialData: props.data, children: /* @__PURE__ */ jsx15(BoardContainer_default, { ...props, id: boardId, components: components2 }) });
969
+ }
970
+ var Board_default = Board;
971
+
972
+ // src/index.ts
973
+ var index_default = Board_default;
974
+ export {
975
+ Board_default as Board,
976
+ Card_default as Card,
977
+ Lane_default as Lane,
978
+ Tag_default as Tag,
979
+ index_default as default
980
+ };
981
+ //# sourceMappingURL=index.mjs.map