gridsmith-ui 0.29.1 → 0.30.1
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/CHANGELOG.md +94 -0
- package/README.md +5 -1
- package/dist/DropdownMenu-DqT7ZQ0D.d.ts +39 -0
- package/dist/charts.js +24 -9
- package/dist/charts.js.map +1 -1
- package/dist/chat.js +7 -7
- package/dist/{chunk-MFAWJSMJ.js → chunk-6C2IUMY7.js} +4 -23
- package/dist/chunk-6C2IUMY7.js.map +1 -0
- package/dist/{chunk-OYNUU346.js → chunk-7QLXYRPD.js} +3 -12
- package/dist/chunk-7QLXYRPD.js.map +1 -0
- package/dist/{chunk-FD4YED7O.js → chunk-DBHVJ7Z3.js} +5 -49
- package/dist/chunk-DBHVJ7Z3.js.map +1 -0
- package/dist/chunk-IELIF4PJ.js +110 -0
- package/dist/chunk-IELIF4PJ.js.map +1 -0
- package/dist/chunk-ISONVOA7.js +316 -0
- package/dist/chunk-ISONVOA7.js.map +1 -0
- package/dist/{chunk-4TUJXDRU.js → chunk-KMKBOTFR.js} +93 -32
- package/dist/chunk-KMKBOTFR.js.map +1 -0
- package/dist/{chunk-MKFWHWBK.js → chunk-L6HWACK7.js} +7 -7
- package/dist/chunk-L6HWACK7.js.map +1 -0
- package/dist/chunk-LJYSVZMI.js +25 -0
- package/dist/chunk-LJYSVZMI.js.map +1 -0
- package/dist/{chunk-6A5RHW2W.js → chunk-NEUWZUK6.js} +4 -5
- package/dist/chunk-NEUWZUK6.js.map +1 -0
- package/dist/chunk-NKUCS67T.js +13 -0
- package/dist/chunk-NKUCS67T.js.map +1 -0
- package/dist/{chunk-RECCCA7Z.js → chunk-OVHBQT3Z.js} +6 -5
- package/dist/chunk-OVHBQT3Z.js.map +1 -0
- package/dist/{chunk-I3MTQ3CS.js → chunk-OX3NNMWG.js} +6 -6
- package/dist/{chunk-I3MTQ3CS.js.map → chunk-OX3NNMWG.js.map} +1 -1
- package/dist/{chunk-ZI3OQDET.js → chunk-R5QXMNW7.js} +297 -15
- package/dist/chunk-R5QXMNW7.js.map +1 -0
- package/dist/chunk-SJZWEBHL.js +58 -0
- package/dist/chunk-SJZWEBHL.js.map +1 -0
- package/dist/{chunk-FUKSLQ77.js → chunk-SLRTSBCJ.js} +91 -51
- package/dist/chunk-SLRTSBCJ.js.map +1 -0
- package/dist/chunk-T52LAFKG.js +1350 -0
- package/dist/chunk-T52LAFKG.js.map +1 -0
- package/dist/chunk-TYNDXMBV.js +105 -0
- package/dist/chunk-TYNDXMBV.js.map +1 -0
- package/dist/{chunk-37WOC7F7.js → chunk-UDJOIVKQ.js} +31 -113
- package/dist/chunk-UDJOIVKQ.js.map +1 -0
- package/dist/{chunk-RJEUQ7RL.js → chunk-ULCACQSA.js} +47 -90
- package/dist/chunk-ULCACQSA.js.map +1 -0
- package/dist/{chunk-HP7WUZPK.js → chunk-WIUGPZOH.js} +10 -3
- package/dist/chunk-WIUGPZOH.js.map +1 -0
- package/dist/{chunk-JTU7OBYJ.js → chunk-YL5KD5RC.js} +4 -3
- package/dist/chunk-YL5KD5RC.js.map +1 -0
- package/dist/data-grid.d.ts +25 -6
- package/dist/data-grid.js +180 -58
- package/dist/data-grid.js.map +1 -1
- package/dist/editor-mentions.js +4 -5
- package/dist/editor-mentions.js.map +1 -1
- package/dist/editor.js +11 -8
- package/dist/editor.js.map +1 -1
- package/dist/{feather-FNBQ74RR.js → feather-XFFEFLTV.js} +5 -3
- package/dist/feather-XFFEFLTV.js.map +1 -0
- package/dist/{heroicons-DODCSYWD.js → heroicons-L6DOHBEA.js} +5 -3
- package/dist/heroicons-L6DOHBEA.js.map +1 -0
- package/dist/icons.d.ts +1 -1
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +138 -39
- package/dist/index.js +846 -1667
- package/dist/index.js.map +1 -1
- package/dist/kanban.d.ts +26 -8
- package/dist/kanban.js +204 -79
- package/dist/kanban.js.map +1 -1
- package/dist/{lucide-ZWE3EPGP.js → lucide-JOORDTO6.js} +5 -3
- package/dist/lucide-JOORDTO6.js.map +1 -0
- package/dist/{phosphor-DIHPA4AJ.js → phosphor-TKGDYNGR.js} +5 -3
- package/dist/phosphor-TKGDYNGR.js.map +1 -0
- package/dist/resizable.js +47 -16
- package/dist/resizable.js.map +1 -1
- package/dist/roots.css +12 -8
- package/dist/{table-utils-CxnpFHRF.d.ts → table-utils-BXkP-2Hx.d.ts} +3 -1
- package/dist/{tabler-D3VYTSGI.js → tabler-YGETLYRU.js} +5 -3
- package/dist/tabler-YGETLYRU.js.map +1 -0
- package/dist/tokens.css +14 -0
- package/dist/utils.js.map +1 -1
- package/dist/virtualized.d.ts +6 -1
- package/dist/virtualized.js +17 -13
- package/dist/virtualized.js.map +1 -1
- package/dist-static/tokens.css +14 -0
- package/package.json +1 -1
- package/dist/chunk-37WOC7F7.js.map +0 -1
- package/dist/chunk-4TUJXDRU.js.map +0 -1
- package/dist/chunk-6A5RHW2W.js.map +0 -1
- package/dist/chunk-FD4YED7O.js.map +0 -1
- package/dist/chunk-FUKSLQ77.js.map +0 -1
- package/dist/chunk-HP7WUZPK.js.map +0 -1
- package/dist/chunk-JTU7OBYJ.js.map +0 -1
- package/dist/chunk-MFAWJSMJ.js.map +0 -1
- package/dist/chunk-MKFWHWBK.js.map +0 -1
- package/dist/chunk-OYNUU346.js.map +0 -1
- package/dist/chunk-QM4SJKWV.js +0 -51
- package/dist/chunk-QM4SJKWV.js.map +0 -1
- package/dist/chunk-QNHVPE2B.js +0 -117
- package/dist/chunk-QNHVPE2B.js.map +0 -1
- package/dist/chunk-RECCCA7Z.js.map +0 -1
- package/dist/chunk-RJEUQ7RL.js.map +0 -1
- package/dist/chunk-WH2FUFAC.js +0 -15
- package/dist/chunk-WH2FUFAC.js.map +0 -1
- package/dist/chunk-ZI3OQDET.js.map +0 -1
- package/dist/feather-FNBQ74RR.js.map +0 -1
- package/dist/heroicons-DODCSYWD.js.map +0 -1
- package/dist/lucide-ZWE3EPGP.js.map +0 -1
- package/dist/phosphor-DIHPA4AJ.js.map +0 -1
- package/dist/tabler-D3VYTSGI.js.map +0 -1
package/dist/kanban.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
+
import { D as DropdownMenuSubmenu, a as DropdownMenuEntry } from './DropdownMenu-DqT7ZQ0D.js';
|
|
4
|
+
import './icons.js';
|
|
3
5
|
|
|
4
6
|
interface KanbanCard {
|
|
5
7
|
id: string;
|
|
@@ -13,6 +15,12 @@ interface KanbanColumn {
|
|
|
13
15
|
title: string;
|
|
14
16
|
cards: KanbanCard[];
|
|
15
17
|
color?: string;
|
|
18
|
+
/**
|
|
19
|
+
* The column's work-in-progress limit, in cards: the header shows its count against it ("4/3"), and a column holding
|
|
20
|
+
* more is marked with a warning icon, its count's words saying so to a screen reader. A limit informs and never refuses
|
|
21
|
+
* a drop; a page that must refuse a move still can, in onCardMove. Default: no limit, the count alone.
|
|
22
|
+
*/
|
|
23
|
+
limit?: number;
|
|
16
24
|
}
|
|
17
25
|
interface KanbanBoardProps {
|
|
18
26
|
/**
|
|
@@ -28,19 +36,22 @@ interface KanbanBoardProps {
|
|
|
28
36
|
*/
|
|
29
37
|
onColumnsChange?: (columns: KanbanColumn[]) => unknown;
|
|
30
38
|
/**
|
|
31
|
-
* A card was
|
|
32
|
-
*
|
|
33
|
-
*
|
|
39
|
+
* A card was moved to a new place: its column and its index there, once on drop, or on a choice from the card's Move
|
|
40
|
+
* menu (the button beside each card that moves it without dragging: to the top of another column, or a step up or
|
|
41
|
+
* down) — a move between columns, into an empty one, or a reorder within one. Save the move here (or in
|
|
42
|
+
* onColumnsChange); a board with neither saves nothing. The index counts the cards the board shows: when `columns` is
|
|
43
|
+
* filtered, it is the place among the cards passed.
|
|
34
44
|
*
|
|
35
45
|
* Return the save's promise (an async function, or a mutation's mutateAsync) and the card stays where it was dropped
|
|
36
46
|
* while it saves, and while an earlier move of the same card still saves, whatever `columns` arrive meanwhile; when
|
|
37
47
|
* it rejects, the card goes back to where `columns` has it, a screen reader hears so, and the app says why (a toast
|
|
38
48
|
* inside the app shell). The save must settle (give a request a timeout): one that never does holds the card. A
|
|
39
|
-
* handler that throws refuses a move the same way (
|
|
40
|
-
* then saves, puts the card back itself when its save fails; the board still says the move was not
|
|
49
|
+
* handler that throws refuses a move the same way (one the person isn't allowed to make). An app that updates
|
|
50
|
+
* `columns` at once, then saves, puts the card back itself when its save fails; the board still says the move was not
|
|
51
|
+
* saved.
|
|
41
52
|
*/
|
|
42
53
|
onCardMove?: (card: KanbanCard, toColumnId: string, index: number) => unknown;
|
|
43
|
-
/** A board to look at, not rearrange: no dragging and no
|
|
54
|
+
/** A board to look at, not rearrange: no dragging, no grab cursor and no Move menu; cards still open with onCardClick. */
|
|
44
55
|
readOnly?: boolean;
|
|
45
56
|
renderCard?: (card: KanbanCard) => ReactNode;
|
|
46
57
|
/**
|
|
@@ -49,8 +60,15 @@ interface KanbanBoardProps {
|
|
|
49
60
|
* renderCard — a role="button" div fights the drag listeners and the judge cites it (Sprint 25.4 K-1).
|
|
50
61
|
*/
|
|
51
62
|
onCardClick?: (card: KanbanCard) => void;
|
|
63
|
+
/**
|
|
64
|
+
* A card's own actions (open, edit its labels, copy a link, delete…), for its ⋯ menu. `move` is the board's Move entry,
|
|
65
|
+
* a submenu of the columns the card can go to and its steps up and down: put it where it belongs among the actions;
|
|
66
|
+
* left out, the board adds it at the end, since it is the way to move a card without dragging (WCAG 2.5.7). Without
|
|
67
|
+
* cardActions the menu is the move choices alone. On a read-only board the menu holds the actions, with no Move.
|
|
68
|
+
*/
|
|
69
|
+
cardActions?: (card: KanbanCard, move: DropdownMenuSubmenu) => DropdownMenuEntry[];
|
|
52
70
|
className?: string;
|
|
53
71
|
}
|
|
54
|
-
declare function KanbanBoard({ columns: columnsProp, onColumnsChange, onCardMove, readOnly, renderCard, onCardClick, className, }: KanbanBoardProps): react_jsx_runtime.JSX.Element;
|
|
72
|
+
declare function KanbanBoard({ columns: columnsProp, onColumnsChange, onCardMove, readOnly, renderCard, onCardClick, cardActions, className, }: KanbanBoardProps): react_jsx_runtime.JSX.Element;
|
|
55
73
|
|
|
56
|
-
export { KanbanBoard };
|
|
74
|
+
export { KanbanBoard, type KanbanBoardProps, type KanbanCard, type KanbanColumn };
|
package/dist/kanban.js
CHANGED
|
@@ -1,26 +1,52 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { DropdownMenu } from './chunk-ISONVOA7.js';
|
|
3
|
+
import { useFillHeight, FillContext } from './chunk-DBHVJ7Z3.js';
|
|
4
|
+
import { useKeyboardScroll } from './chunk-SJZWEBHL.js';
|
|
5
|
+
import { Badge } from './chunk-OVHBQT3Z.js';
|
|
6
|
+
import { useFramesItself, Button } from './chunk-ULCACQSA.js';
|
|
7
|
+
import './chunk-NKUCS67T.js';
|
|
8
|
+
import './chunk-ZHUVLBCC.js';
|
|
5
9
|
import { useRevealFocus } from './chunk-HTXP5BMM.js';
|
|
6
|
-
import './chunk-
|
|
7
|
-
import { useTextOverflow
|
|
10
|
+
import './chunk-R5QXMNW7.js';
|
|
11
|
+
import { useTextOverflow } from './chunk-IELIF4PJ.js';
|
|
12
|
+
import { AutoTooltip } from './chunk-UDJOIVKQ.js';
|
|
13
|
+
import { Icon } from './chunk-L6HWACK7.js';
|
|
8
14
|
import './chunk-7Q46JXSK.js';
|
|
9
|
-
import
|
|
10
|
-
import './chunk-
|
|
11
|
-
import './chunk-WH2FUFAC.js';
|
|
12
|
-
import { cn, FOCUS_INSET, FOCUS_RING } from './chunk-JTU7OBYJ.js';
|
|
15
|
+
import './chunk-WIUGPZOH.js';
|
|
16
|
+
import { cn, FOCUS_INSET, FOCUS_RING } from './chunk-YL5KD5RC.js';
|
|
13
17
|
import { useState, useRef, useLayoutEffect, useMemo, useCallback, useContext } from 'react';
|
|
14
18
|
import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCorners, DragOverlay, useDroppable } from '@dnd-kit/core';
|
|
15
19
|
import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, useSortable, arrayMove } from '@dnd-kit/sortable';
|
|
16
20
|
import { CSS } from '@dnd-kit/utilities';
|
|
17
21
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
18
22
|
|
|
23
|
+
var MOVE_ROOM_TITLE = "pr-[var(--ds-control-height-sm)]";
|
|
24
|
+
var MOVE_ROOM_CARD = "pr-[calc(var(--ds-space-3)+var(--ds-control-height-sm))]";
|
|
25
|
+
function cardMenuOf(card, moveEntries, cardActions) {
|
|
26
|
+
if (!cardActions) return moveEntries;
|
|
27
|
+
const move = { label: "Move", items: moveEntries };
|
|
28
|
+
const own = cardActions(card, move);
|
|
29
|
+
const isMove = (e) => e === move || "items" in e && e.items === move.items;
|
|
30
|
+
const divider = { type: "divider" };
|
|
31
|
+
const entries = moveEntries.length === 0 ? own.filter((e) => !isMove(e)) : own.some(isMove) ? own : [...own, divider, move];
|
|
32
|
+
const isDivider = (e) => !!e && "type" in e && e.type === "divider";
|
|
33
|
+
return entries.reduce((acc, e) => isDivider(e) && (acc.length === 0 || isDivider(acc[acc.length - 1])) ? acc : [...acc, e], []).filter((e, i, all) => !(isDivider(e) && i === all.length - 1));
|
|
34
|
+
}
|
|
35
|
+
function menuOf(moves, choose) {
|
|
36
|
+
const entry = (m) => ({ label: m.label, onClick: () => choose(m) });
|
|
37
|
+
const others = moves.filter((m) => m.step === 0).map(entry);
|
|
38
|
+
const steps = moves.filter((m) => m.step !== 0).map(entry);
|
|
39
|
+
return others.length && steps.length ? [...others, { type: "divider" }, ...steps] : [...others, ...steps];
|
|
40
|
+
}
|
|
41
|
+
var MOVE_PLACE = "absolute top-ds-1.5 right-ds-1.5";
|
|
19
42
|
function SortableCard({
|
|
20
43
|
card,
|
|
21
44
|
renderCard,
|
|
22
45
|
onCardClick,
|
|
23
|
-
readOnly
|
|
46
|
+
readOnly,
|
|
47
|
+
moves,
|
|
48
|
+
onMove,
|
|
49
|
+
cardActions
|
|
24
50
|
}) {
|
|
25
51
|
const {
|
|
26
52
|
attributes,
|
|
@@ -30,9 +56,34 @@ function SortableCard({
|
|
|
30
56
|
transition,
|
|
31
57
|
isDragging
|
|
32
58
|
} = useSortable({ id: card.id, disabled: readOnly });
|
|
59
|
+
const entries = cardMenuOf(card, readOnly ? [] : menuOf(moves, (m) => onMove(card.id, m.to, m.step)), cardActions);
|
|
60
|
+
const renderMenu = (style2, faded) => /* @__PURE__ */ jsx(
|
|
61
|
+
DropdownMenu,
|
|
62
|
+
{
|
|
63
|
+
items: entries,
|
|
64
|
+
trigger: (
|
|
65
|
+
// the library's icon button: its name says what its menu is for, and it shows that name as its one tooltip;
|
|
66
|
+
// shown at all times, since a phone has no hover; quiet, as a Card's ⋯ is
|
|
67
|
+
/* @__PURE__ */ jsx(
|
|
68
|
+
Button,
|
|
69
|
+
{
|
|
70
|
+
type: "button",
|
|
71
|
+
variant: "ghost",
|
|
72
|
+
size: "icon-sm",
|
|
73
|
+
"aria-label": cardActions ? `Actions for ${card.title}` : `Move ${card.title}`,
|
|
74
|
+
"data-kanban-move": card.id,
|
|
75
|
+
style: style2,
|
|
76
|
+
className: cn(MOVE_PLACE, "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)]", faded && "opacity-30"),
|
|
77
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: 14 })
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
const menu = entries.length > 0 ? renderMenu : null;
|
|
33
84
|
if (readOnly) {
|
|
34
85
|
const open = onCardClick ? () => onCardClick(card) : void 0;
|
|
35
|
-
|
|
86
|
+
const surface = /* @__PURE__ */ jsx(
|
|
36
87
|
"div",
|
|
37
88
|
{
|
|
38
89
|
ref: setNodeRef,
|
|
@@ -48,47 +99,63 @@ function SortableCard({
|
|
|
48
99
|
className: cn(
|
|
49
100
|
"rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
|
|
50
101
|
"bg-[var(--ds-bg-surface)] p-ds-3 shadow-[var(--ds-shadow-button)]",
|
|
102
|
+
// a card with its actions' ⋯ keeps the button's room clear, as a card that moves does
|
|
103
|
+
menu && renderCard && MOVE_ROOM_CARD,
|
|
51
104
|
open ? "cursor-pointer" : "cursor-default",
|
|
52
105
|
FOCUS_RING
|
|
53
106
|
),
|
|
54
|
-
children: renderCard ? renderCard(card) : /* @__PURE__ */ jsx(DefaultCard, { card })
|
|
107
|
+
children: renderCard ? renderCard(card) : /* @__PURE__ */ jsx(DefaultCard, { card, moveRoom: !!menu })
|
|
55
108
|
}
|
|
56
109
|
);
|
|
110
|
+
return menu ? /* @__PURE__ */ jsxs("div", { className: "relative", "data-kanban-item": card.id, children: [
|
|
111
|
+
surface,
|
|
112
|
+
menu(void 0, false)
|
|
113
|
+
] }) : surface;
|
|
57
114
|
}
|
|
58
115
|
const style = {
|
|
59
116
|
transform: CSS.Transform.toString(transform),
|
|
60
117
|
transition
|
|
61
118
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
119
|
+
const movable = !!menu;
|
|
120
|
+
return (
|
|
121
|
+
// Sprint 25.8.7 item 3 (WCAG 2.5.7): the card's slot holds the card and, beside it, its Move button, never one
|
|
122
|
+
// inside the other (a control inside a control: the card is the drag's button). The card stays the node the drag
|
|
123
|
+
// measures and moves; the button takes the same transform, so it travels with the card as the list sorts.
|
|
124
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", "data-kanban-item": card.id, children: [
|
|
125
|
+
/* @__PURE__ */ jsx(
|
|
126
|
+
"div",
|
|
127
|
+
{
|
|
128
|
+
ref: setNodeRef,
|
|
129
|
+
style,
|
|
130
|
+
...attributes,
|
|
131
|
+
...listeners,
|
|
132
|
+
onClick: onCardClick ? () => onCardClick(card) : void 0,
|
|
133
|
+
onKeyDown: (e) => {
|
|
134
|
+
if (onCardClick && e.key === "Enter") {
|
|
135
|
+
e.preventDefault();
|
|
136
|
+
onCardClick(card);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
listeners?.onKeyDown?.(e);
|
|
140
|
+
},
|
|
141
|
+
"data-kanban-card": card.id,
|
|
142
|
+
className: cn(
|
|
143
|
+
"rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
|
|
144
|
+
"bg-[var(--ds-bg-surface)] p-ds-3 active:cursor-grabbing",
|
|
145
|
+
movable && renderCard && MOVE_ROOM_CARD,
|
|
146
|
+
onCardClick ? "cursor-pointer" : "cursor-grab",
|
|
147
|
+
"shadow-[var(--ds-shadow-button)]",
|
|
148
|
+
"transition-shadow duration-[var(--ds-transition-speed)]",
|
|
149
|
+
FOCUS_RING,
|
|
150
|
+
// no scale on the card left behind: the drag overlay takes its size from it, and 2% wider made the cards
|
|
151
|
+
// below count as "to the right" of it, so a keyboard drag's arrow keys landed on them (Sprint 25.7, C2)
|
|
152
|
+
isDragging && "opacity-30"
|
|
153
|
+
),
|
|
154
|
+
children: renderCard ? renderCard(card) : /* @__PURE__ */ jsx(DefaultCard, { card, moveRoom: movable })
|
|
75
155
|
}
|
|
76
|
-
listeners?.onKeyDown?.(e);
|
|
77
|
-
},
|
|
78
|
-
"data-kanban-card": card.id,
|
|
79
|
-
className: cn(
|
|
80
|
-
"rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
|
|
81
|
-
"bg-[var(--ds-bg-surface)] p-ds-3 active:cursor-grabbing",
|
|
82
|
-
onCardClick ? "cursor-pointer" : "cursor-grab",
|
|
83
|
-
"shadow-[var(--ds-shadow-button)]",
|
|
84
|
-
"transition-shadow duration-[var(--ds-transition-speed)]",
|
|
85
|
-
FOCUS_RING,
|
|
86
|
-
// no scale on the card left behind: the drag overlay takes its size from it, and 2% wider made the cards
|
|
87
|
-
// below count as "to the right" of it, so a keyboard drag's arrow keys landed on them (Sprint 25.7, C2)
|
|
88
|
-
isDragging && "opacity-30"
|
|
89
156
|
),
|
|
90
|
-
|
|
91
|
-
}
|
|
157
|
+
menu && menu(style, isDragging)
|
|
158
|
+
] })
|
|
92
159
|
);
|
|
93
160
|
}
|
|
94
161
|
function ClampedDescription({ children }) {
|
|
@@ -97,31 +164,36 @@ function ClampedDescription({ children }) {
|
|
|
97
164
|
if (!isTruncated) return el;
|
|
98
165
|
return /* @__PURE__ */ jsx(AutoTooltip, { content: children, children: el });
|
|
99
166
|
}
|
|
100
|
-
function TruncTitle({ children }) {
|
|
167
|
+
function TruncTitle({ children, className }) {
|
|
101
168
|
const { ref, isTruncated } = useTextOverflow({ lines: 1 });
|
|
102
|
-
const el = /* @__PURE__ */ jsx("p", { ref, className: "text-sm font-medium text-[var(--ds-text-primary)] truncate", children });
|
|
169
|
+
const el = /* @__PURE__ */ jsx("p", { ref, className: cn("text-sm font-medium text-[var(--ds-text-primary)] truncate", className), children });
|
|
103
170
|
if (!isTruncated) return el;
|
|
104
171
|
return /* @__PURE__ */ jsx(AutoTooltip, { content: children, children: el });
|
|
105
172
|
}
|
|
106
|
-
function DefaultCard({ card }) {
|
|
173
|
+
function DefaultCard({ card, moveRoom }) {
|
|
107
174
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-ds-2", children: [
|
|
108
|
-
/* @__PURE__ */ jsx(TruncTitle, { children: card.title }),
|
|
175
|
+
/* @__PURE__ */ jsx(TruncTitle, { className: moveRoom ? MOVE_ROOM_TITLE : void 0, children: card.title }),
|
|
109
176
|
card.description && /* @__PURE__ */ jsx(ClampedDescription, { children: card.description }),
|
|
110
177
|
card.tags && card.tags.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-ds-1", children: card.tags.map((tag) => /* @__PURE__ */ jsx(Badge, { variant: "subtle", size: "sm", children: tag }, tag)) })
|
|
111
178
|
] });
|
|
112
179
|
}
|
|
113
180
|
function OverlayCard({
|
|
114
181
|
card,
|
|
115
|
-
renderCard
|
|
182
|
+
renderCard,
|
|
183
|
+
moveRoom
|
|
116
184
|
}) {
|
|
117
|
-
return /* @__PURE__ */
|
|
185
|
+
return /* @__PURE__ */ jsxs(
|
|
118
186
|
"div",
|
|
119
187
|
{
|
|
120
188
|
className: cn(
|
|
121
|
-
"rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
|
|
122
|
-
"bg-[var(--ds-bg-surface)] p-ds-3 shadow-[var(--ds-shadow-card)]"
|
|
189
|
+
"relative rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
|
|
190
|
+
"bg-[var(--ds-bg-surface)] p-ds-3 shadow-[var(--ds-shadow-card)]",
|
|
191
|
+
moveRoom && renderCard && MOVE_ROOM_CARD
|
|
123
192
|
),
|
|
124
|
-
children:
|
|
193
|
+
children: [
|
|
194
|
+
renderCard ? renderCard(card) : /* @__PURE__ */ jsx(DefaultCard, { card, moveRoom }),
|
|
195
|
+
moveRoom && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn(MOVE_PLACE, "inline-flex items-center justify-center h-[var(--ds-control-height-sm)] w-[var(--ds-control-height-sm)] text-[var(--ds-text-muted)]"), children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: 14 }) })
|
|
196
|
+
]
|
|
125
197
|
}
|
|
126
198
|
);
|
|
127
199
|
}
|
|
@@ -194,6 +266,21 @@ function keptHolds(holds, cols) {
|
|
|
194
266
|
var replayHolds = (cols, holds) => holds.reduce(place, cols);
|
|
195
267
|
var isThenable = (value) => value != null && typeof value.then === "function";
|
|
196
268
|
var placeWords = (at) => `${at.col.title}, position ${at.index + 1} of ${at.col.cards.length}`;
|
|
269
|
+
var overLimit = (col) => col.limit != null && col.cards.length > col.limit;
|
|
270
|
+
var overLimitWords = (col) => overLimit(col) ? ` ${col.title} is over its limit of ${col.limit}.` : "";
|
|
271
|
+
var countWords = (count, limit) => `${count} ${count === 1 ? "card" : "cards"}, ${count > limit ? `over the limit of ${limit}` : `limit ${limit}`}`;
|
|
272
|
+
function ColumnCount({ count, limit }) {
|
|
273
|
+
if (limit == null) return /* @__PURE__ */ jsx("span", { className: "text-xs text-[var(--ds-text-muted)] tabular-nums", children: count });
|
|
274
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-ds-1 text-xs text-[var(--ds-text-muted)] tabular-nums", children: [
|
|
275
|
+
/* @__PURE__ */ jsxs("span", { "aria-hidden": "true", children: [
|
|
276
|
+
count,
|
|
277
|
+
"/",
|
|
278
|
+
limit
|
|
279
|
+
] }),
|
|
280
|
+
count > limit && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", "data-kanban-over-limit": "", className: "inline-flex shrink-0 text-[var(--ds-status-warning)]", children: /* @__PURE__ */ jsx(Icon, { name: "warning", size: 14 }) }),
|
|
281
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: countWords(count, limit) })
|
|
282
|
+
] });
|
|
283
|
+
}
|
|
197
284
|
var MOVE_INSTRUCTIONS = "Press Space to pick up the card. Use the Left and Right arrow keys to move it between columns, and the Up and Down arrow keys to move it within a column. Press Space again to drop it, or Escape to cancel.";
|
|
198
285
|
function BoardColumn({
|
|
199
286
|
col,
|
|
@@ -228,7 +315,7 @@ function BoardColumn({
|
|
|
228
315
|
}
|
|
229
316
|
),
|
|
230
317
|
/* @__PURE__ */ jsx("span", { className: "text-sm font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", children: col.title }),
|
|
231
|
-
/* @__PURE__ */ jsx(
|
|
318
|
+
/* @__PURE__ */ jsx(ColumnCount, { count: col.cards.length, limit: col.limit })
|
|
232
319
|
] }),
|
|
233
320
|
/* @__PURE__ */ jsx(Icon, { name: "plus", size: 14, className: "text-[var(--ds-text-muted)]" })
|
|
234
321
|
] }),
|
|
@@ -266,6 +353,7 @@ function KanbanBoard({
|
|
|
266
353
|
readOnly,
|
|
267
354
|
renderCard,
|
|
268
355
|
onCardClick,
|
|
356
|
+
cardActions,
|
|
269
357
|
className
|
|
270
358
|
}) {
|
|
271
359
|
useFramesItself();
|
|
@@ -329,13 +417,13 @@ function KanbanBoard({
|
|
|
329
417
|
const fill = useFillHeight(rootRef);
|
|
330
418
|
const inPane = useContext(FillContext);
|
|
331
419
|
const filled = fill != null || inPane;
|
|
420
|
+
const cardPart = (attr, id) => [...rootRef.current?.querySelectorAll(`[${attr}]`) ?? []].find((el) => el.getAttribute(attr) === id);
|
|
332
421
|
const refocus = useRef(null);
|
|
333
422
|
useLayoutEffect(() => {
|
|
334
|
-
const
|
|
335
|
-
if (
|
|
423
|
+
const target = refocus.current;
|
|
424
|
+
if (target == null) return;
|
|
336
425
|
refocus.current = null;
|
|
337
|
-
|
|
338
|
-
[...cards].find((el) => el.getAttribute("data-kanban-card") === id)?.focus();
|
|
426
|
+
cardPart(target.part === "move" ? "data-kanban-move" : "data-kanban-card", target.id)?.focus();
|
|
339
427
|
});
|
|
340
428
|
const holdKey = useRef(0);
|
|
341
429
|
const sensors = useSensors(
|
|
@@ -389,8 +477,11 @@ function KanbanBoard({
|
|
|
389
477
|
const back = locate(shown, hold.cardId);
|
|
390
478
|
const card = back?.col.cards[back.index];
|
|
391
479
|
const focused = document.activeElement;
|
|
392
|
-
|
|
393
|
-
|
|
480
|
+
const inSlot = !!focused && !!rootRef.current?.contains(focused) && focused.closest?.("[data-kanban-item]")?.getAttribute("data-kanban-item") === hold.cardId;
|
|
481
|
+
const menuOpen = cardPart("data-kanban-move", hold.cardId)?.getAttribute("aria-expanded") === "true";
|
|
482
|
+
if (inSlot) refocus.current = { id: hold.cardId, part: focused?.hasAttribute("data-kanban-move") ? "move" : "card" };
|
|
483
|
+
else if (menuOpen && locate(replayHolds(columnsRef.current, holdsRef.current), hold.cardId)?.col.id !== back?.col.id) {
|
|
484
|
+
refocus.current = { id: hold.cardId, part: "move" };
|
|
394
485
|
}
|
|
395
486
|
setHolds(rest);
|
|
396
487
|
if (previewRef.current && activeRef.current) setPreview(rebase(shown, previewRef.current, activeRef.current));
|
|
@@ -406,32 +497,20 @@ function KanbanBoard({
|
|
|
406
497
|
const next = holdsRef.current.map((h) => h === hold ? { ...h, inFlight, layout: inFlight > 0 ? h.layout : layoutOf(columnsRef.current) } : h);
|
|
407
498
|
setHolds(inFlight > 0 ? next : keptHolds(next, columnsRef.current));
|
|
408
499
|
};
|
|
409
|
-
const
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
const cols = previewRef.current ?? committed;
|
|
413
|
-
activeRef.current = null;
|
|
414
|
-
setActiveCard(null);
|
|
415
|
-
setPreview(null);
|
|
416
|
-
const final = over ? landing(cols, activeId, String(over.id)) : committed;
|
|
417
|
-
const before = locate(committed, activeId);
|
|
418
|
-
const after = locate(final, activeId);
|
|
419
|
-
if (!before || !after || before.col.id === after.col.id && before.index === after.index) {
|
|
420
|
-
if (before) said.current = `Dropped ${before.col.cards[before.index].title} back in ${placeWords(before)}.`;
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
said.current = `Dropped ${after.col.cards[after.index].title} in ${placeWords(after)}.`;
|
|
500
|
+
const commit = (final, cardId) => {
|
|
501
|
+
const after = locate(final, cardId);
|
|
502
|
+
if (!after) return;
|
|
424
503
|
const key = ++holdKey.current;
|
|
425
|
-
const prev = holdsRef.current.find((h) => h.cardId ===
|
|
504
|
+
const prev = holdsRef.current.find((h) => h.cardId === cardId);
|
|
426
505
|
const hold = {
|
|
427
|
-
...placementIn(final,
|
|
506
|
+
...placementIn(final, cardId),
|
|
428
507
|
key,
|
|
429
508
|
inFlight: prev?.inFlight ?? 0,
|
|
430
|
-
origin: prev?.origin ?? placementIn(columnsProp,
|
|
509
|
+
origin: prev?.origin ?? placementIn(columnsProp, cardId) ?? placementIn(committed, cardId),
|
|
431
510
|
earlier: prev ? [...prev.earlier, { cardId: prev.cardId, toColumnId: prev.toColumnId, beforeId: prev.beforeId, index: prev.index }] : [],
|
|
432
511
|
layout: layoutOf(columnsProp)
|
|
433
512
|
};
|
|
434
|
-
setHolds([...holdsRef.current.filter((h) => h.cardId !==
|
|
513
|
+
setHolds([...holdsRef.current.filter((h) => h.cardId !== cardId), hold]);
|
|
435
514
|
const saves = [];
|
|
436
515
|
try {
|
|
437
516
|
for (const result of [onColumnsChange?.(final), onCardMove?.(after.col.cards[after.index], after.col.id, after.index)]) {
|
|
@@ -443,8 +522,51 @@ function KanbanBoard({
|
|
|
443
522
|
}
|
|
444
523
|
if (!saves.length) return;
|
|
445
524
|
setHolds(holdsRef.current.map((h) => h.key === key ? { ...h, inFlight: h.inFlight + 1 } : h));
|
|
446
|
-
Promise.allSettled(saves).then((results) => settle(
|
|
525
|
+
Promise.allSettled(saves).then((results) => settle(cardId, key, results.every((r) => r.status === "fulfilled")));
|
|
526
|
+
};
|
|
527
|
+
const handleDragEnd = (event) => {
|
|
528
|
+
const { active, over } = event;
|
|
529
|
+
const activeId = String(active.id);
|
|
530
|
+
const cols = previewRef.current ?? committed;
|
|
531
|
+
activeRef.current = null;
|
|
532
|
+
setActiveCard(null);
|
|
533
|
+
setPreview(null);
|
|
534
|
+
const final = over ? landing(cols, activeId, String(over.id)) : committed;
|
|
535
|
+
const before = locate(committed, activeId);
|
|
536
|
+
const after = locate(final, activeId);
|
|
537
|
+
if (!before || !after || before.col.id === after.col.id && before.index === after.index) {
|
|
538
|
+
if (before) said.current = `Dropped ${before.col.cards[before.index].title} back in ${placeWords(before)}.`;
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
said.current = `Dropped ${after.col.cards[after.index].title} in ${placeWords(after)}.${overLimitWords(after.col)}`;
|
|
542
|
+
commit(final, activeId);
|
|
543
|
+
};
|
|
544
|
+
const moveFromMenu = (cardId, toColumnId, step = 0) => {
|
|
545
|
+
const at = locate(committed, cardId);
|
|
546
|
+
const to = committed.find((col) => col.id === toColumnId);
|
|
547
|
+
if (!at || !to) return;
|
|
548
|
+
const index = to.id === at.col.id ? at.index + step : 0;
|
|
549
|
+
const beforeId = to.cards.filter((c) => c.id !== cardId)[index]?.id ?? null;
|
|
550
|
+
const final = place(committed, { cardId, toColumnId, beforeId, index });
|
|
551
|
+
const after = locate(final, cardId);
|
|
552
|
+
if (!after || after.col.id === at.col.id && after.index === at.index) return;
|
|
553
|
+
noticeParts.current = [];
|
|
554
|
+
setNotice(`Moved ${at.col.cards[at.index].title} to ${placeWords(after)}.${overLimitWords(after.col)}`);
|
|
555
|
+
commit(final, cardId);
|
|
556
|
+
refocus.current = { id: cardId, part: "move" };
|
|
557
|
+
};
|
|
558
|
+
const movesFor = (col, index) => {
|
|
559
|
+
if (readOnly) return [];
|
|
560
|
+
const others = columns.filter((c) => c.id !== col.id).map((c) => ({ label: `Move to ${c.title}`, to: c.id, step: 0 }));
|
|
561
|
+
const steps = [
|
|
562
|
+
...index > 0 ? [{ label: "Move up", to: col.id, step: -1 }] : [],
|
|
563
|
+
...index < col.cards.length - 1 ? [{ label: "Move down", to: col.id, step: 1 }] : []
|
|
564
|
+
];
|
|
565
|
+
return [...others, ...steps];
|
|
447
566
|
};
|
|
567
|
+
const activeAt = activeCard ? locate(columns, activeCard.id) : null;
|
|
568
|
+
const activeMovable = !!activeAt && cardMenuOf(activeAt.col.cards[activeAt.index], menuOf(movesFor(activeAt.col, activeAt.index), () => {
|
|
569
|
+
}), cardActions).length > 0;
|
|
448
570
|
const handleDragCancel = (event) => {
|
|
449
571
|
activeRef.current = null;
|
|
450
572
|
setActiveCard(null);
|
|
@@ -464,19 +586,22 @@ function KanbanBoard({
|
|
|
464
586
|
onDragCancel: handleDragCancel,
|
|
465
587
|
children: [
|
|
466
588
|
/* @__PURE__ */ jsxs("div", { ref: setRoot, "data-fill": fill != null ? "" : void 0, className: cn("relative flex gap-ds-4 overflow-x-auto pb-ds-2 scroll-px-[4px] snap-x snap-mandatory sm:snap-none", inPane && "h-full min-h-0", className), style: fill != null ? { height: fill } : void 0, children: [
|
|
467
|
-
columns.map((col) => /* @__PURE__ */ jsx(BoardColumn, { col, readOnly, filled, children: col.cards.map((card) => /* @__PURE__ */ jsx(
|
|
589
|
+
columns.map((col) => /* @__PURE__ */ jsx(BoardColumn, { col, readOnly, filled, children: col.cards.map((card, index) => /* @__PURE__ */ jsx(
|
|
468
590
|
SortableCard,
|
|
469
591
|
{
|
|
470
592
|
card,
|
|
471
593
|
renderCard,
|
|
472
594
|
onCardClick,
|
|
473
|
-
readOnly
|
|
595
|
+
readOnly,
|
|
596
|
+
moves: movesFor(col, index),
|
|
597
|
+
onMove: moveFromMenu,
|
|
598
|
+
cardActions
|
|
474
599
|
},
|
|
475
600
|
card.id
|
|
476
601
|
)) }, col.id)),
|
|
477
602
|
/* @__PURE__ */ jsx("div", { role: "status", className: "sr-only", children: notice })
|
|
478
603
|
] }),
|
|
479
|
-
/* @__PURE__ */ jsx(DragOverlay, { children: activeCard && /* @__PURE__ */ jsx(OverlayCard, { card: activeCard, renderCard }) })
|
|
604
|
+
/* @__PURE__ */ jsx(DragOverlay, { children: activeCard && /* @__PURE__ */ jsx(OverlayCard, { card: activeCard, renderCard, moveRoom: activeMovable }) })
|
|
480
605
|
]
|
|
481
606
|
}
|
|
482
607
|
);
|