@happyvertical/smrt-projects 0.40.65 → 0.40.67
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.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +489 -420
- package/dist/index.js.map +1 -1
- package/dist/managed-client.d.ts +9 -0
- package/dist/managed-client.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/playground.js +48 -3
- package/dist/playground.js.map +1 -1
- package/dist/project-board-types.d.ts +12 -0
- package/dist/project-board-types.d.ts.map +1 -0
- package/dist/project-board-types.js +0 -0
- package/dist/services/delivery-service.d.ts +1 -1
- package/dist/services/delivery-service.d.ts.map +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/project-board-service.d.ts +32 -0
- package/dist/services/project-board-service.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +1916 -91
- package/dist/svelte/DevelopmentBoard.svelte +54 -74
- package/dist/svelte/DevelopmentBoard.svelte.d.ts.map +1 -1
- package/dist/svelte/ProjectBoard.svelte +157 -0
- package/dist/svelte/ProjectBoard.svelte.d.ts +32 -0
- package/dist/svelte/ProjectBoard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/__tests__/ProjectBoard.test.js +156 -0
- package/dist/svelte/i18n.d.ts +2 -0
- package/dist/svelte/i18n.d.ts.map +1 -1
- package/dist/svelte/i18n.js +2 -0
- package/dist/svelte/index.d.ts +4 -1
- package/dist/svelte/index.d.ts.map +1 -1
- package/dist/svelte/index.js +3 -1
- package/dist/svelte/playground.d.ts +99 -0
- package/dist/svelte/playground.d.ts.map +1 -1
- package/dist/svelte/playground.js +49 -3
- package/dist/svelte/project-board-types.d.ts +3 -0
- package/dist/svelte/project-board-types.d.ts.map +1 -0
- package/dist/svelte/project-board-types.js +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +10 -1
- package/dist/ui.js.map +1 -1
- package/package.json +17 -16
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
Board,
|
|
4
|
+
type BoardCard,
|
|
5
|
+
type BoardColumn,
|
|
6
|
+
} from '@happyvertical/smrt-svelte/board';
|
|
2
7
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
3
8
|
import type { DevelopmentRequestView } from './delivery-types.js';
|
|
4
9
|
import { M } from './i18n.js';
|
|
5
10
|
|
|
11
|
+
interface DevelopmentBoardCard extends BoardCard {
|
|
12
|
+
request: DevelopmentRequestView;
|
|
13
|
+
columnId: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
6
16
|
export interface Props {
|
|
7
17
|
requests?: DevelopmentRequestView[];
|
|
8
18
|
columns?: string[];
|
|
@@ -15,89 +25,59 @@ let {
|
|
|
15
25
|
onselect,
|
|
16
26
|
}: Props = $props();
|
|
17
27
|
const { t } = useI18n();
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
const boardColumns = $derived<BoardColumn[]>(
|
|
29
|
+
columns.map((column) => ({
|
|
30
|
+
id: column.toLowerCase(),
|
|
31
|
+
label: column.replaceAll('_', ' '),
|
|
32
|
+
})),
|
|
33
|
+
);
|
|
34
|
+
const cards = $derived<DevelopmentBoardCard[]>(
|
|
35
|
+
requests.map((request) => ({
|
|
36
|
+
id: request.id,
|
|
37
|
+
request,
|
|
38
|
+
columnId: (request.deliveryStatus || request.status).toLowerCase(),
|
|
39
|
+
})),
|
|
40
|
+
);
|
|
41
|
+
const cardLabel = (card: DevelopmentBoardCard) =>
|
|
42
|
+
card.request.title ?? card.request.description;
|
|
24
43
|
</script>
|
|
25
44
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
{#if requests.length === 0}
|
|
46
|
+
<section
|
|
47
|
+
class="empty"
|
|
48
|
+
aria-label={t(M['projects.development_board.aria'])}
|
|
49
|
+
>
|
|
50
|
+
<p>{t(M['projects.development_board.empty'])}</p>
|
|
51
|
+
</section>
|
|
52
|
+
{:else}
|
|
53
|
+
<Board
|
|
54
|
+
columns={boardColumns}
|
|
55
|
+
{cards}
|
|
56
|
+
label={t(M['projects.development_board.aria'])}
|
|
57
|
+
getCardColumnId={(card) => card.columnId}
|
|
58
|
+
setCardColumnId={(card, columnId) => ({ ...card, columnId })}
|
|
59
|
+
getCardLabel={cardLabel}
|
|
60
|
+
onselect={(card) => onselect?.(card.request)}
|
|
61
|
+
>
|
|
62
|
+
{#snippet card({ card })}
|
|
63
|
+
<strong>{card.request.title ?? card.request.description}</strong>
|
|
64
|
+
<small>
|
|
65
|
+
{card.request.type}
|
|
66
|
+
{card.request.requesterLabel
|
|
67
|
+
? ` · ${card.request.requesterLabel}`
|
|
68
|
+
: ''}
|
|
69
|
+
</small>
|
|
70
|
+
{/snippet}
|
|
71
|
+
</Board>
|
|
72
|
+
{/if}
|
|
52
73
|
|
|
53
74
|
<style>
|
|
54
|
-
.board {
|
|
55
|
-
display: grid;
|
|
56
|
-
gap: var(--smrt-spacing-5);
|
|
57
|
-
grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
|
|
58
|
-
overflow-x: auto;
|
|
59
|
-
}
|
|
60
|
-
.lane {
|
|
61
|
-
min-width: 0;
|
|
62
|
-
}
|
|
63
75
|
.empty {
|
|
64
76
|
color: var(--smrt-color-on-surface-variant);
|
|
65
|
-
grid-column: 1 / -1;
|
|
66
77
|
margin: 0;
|
|
67
78
|
padding: var(--smrt-spacing-5) var(--smrt-spacing-1);
|
|
68
79
|
}
|
|
69
|
-
|
|
70
|
-
align-items: center;
|
|
71
|
-
border-bottom: 2px solid var(--smrt-color-primary);
|
|
72
|
-
display: flex;
|
|
73
|
-
justify-content: space-between;
|
|
74
|
-
padding: var(--smrt-spacing-2) var(--smrt-spacing-1);
|
|
75
|
-
}
|
|
76
|
-
.lane h3 {
|
|
77
|
-
font-size: var(--smrt-typography-label-medium-size);
|
|
78
|
-
margin: 0;
|
|
79
|
-
text-transform: uppercase;
|
|
80
|
-
}
|
|
81
|
-
.lane__items {
|
|
82
|
-
display: grid;
|
|
83
|
-
}
|
|
84
|
-
.lane__items button {
|
|
85
|
-
background: transparent;
|
|
86
|
-
border: 0;
|
|
87
|
-
border-bottom: 1px solid var(--smrt-color-outline-variant);
|
|
88
|
-
color: inherit;
|
|
89
|
-
cursor: pointer;
|
|
90
|
-
display: grid;
|
|
91
|
-
gap: var(--smrt-spacing-1);
|
|
92
|
-
padding: var(--smrt-spacing-4) var(--smrt-spacing-1);
|
|
93
|
-
text-align: left;
|
|
94
|
-
transition: transform 120ms ease;
|
|
95
|
-
}
|
|
96
|
-
.lane__items button:hover,
|
|
97
|
-
.lane__items button:focus-visible {
|
|
98
|
-
transform: translateX(var(--smrt-spacing-1));
|
|
99
|
-
}
|
|
100
|
-
.lane small {
|
|
80
|
+
small {
|
|
101
81
|
color: var(--smrt-color-on-surface-variant);
|
|
102
82
|
}
|
|
103
83
|
</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DevelopmentBoard.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/DevelopmentBoard.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"DevelopmentBoard.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/DevelopmentBoard.svelte.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AASlE,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,EAAE,sBAAsB,EAAE,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;CACtD;AAsDD,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
Board,
|
|
4
|
+
type BoardCard,
|
|
5
|
+
type BoardColumn,
|
|
6
|
+
type BoardMoveIntent,
|
|
7
|
+
} from '@happyvertical/smrt-svelte/board';
|
|
8
|
+
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
9
|
+
import type { ProjectItem, ProjectStatus } from '../types.js';
|
|
10
|
+
import { M } from './i18n.js';
|
|
11
|
+
import type { ProjectBoardMoveIntent } from './project-board-types.js';
|
|
12
|
+
|
|
13
|
+
interface ProjectBoardColumn extends BoardColumn {
|
|
14
|
+
status?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface ProjectBoardCard extends BoardCard {
|
|
18
|
+
item: ProjectItem;
|
|
19
|
+
columnId: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface ProjectBoardBaseProps {
|
|
23
|
+
/** Provider project identity, forwarded only to the injected move action. */
|
|
24
|
+
projectId: string;
|
|
25
|
+
/** Canonical, ordered provider statuses. Their supplied order is preserved. */
|
|
26
|
+
statuses: readonly ProjectStatus[];
|
|
27
|
+
/** Authoritative provider items; this adapter never reorders them locally. */
|
|
28
|
+
items: readonly ProjectItem[];
|
|
29
|
+
onselect?: (item: ProjectItem) => void;
|
|
30
|
+
label?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ReadOnlyProjectBoardProps {
|
|
34
|
+
/** Omit both callbacks to expose a read-only controlled board. */
|
|
35
|
+
onmove?: never;
|
|
36
|
+
onrefresh?: never;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface MovableProjectBoardProps {
|
|
40
|
+
/**
|
|
41
|
+
* Browser-safe mutation boundary. Its consumer attaches authorization in a
|
|
42
|
+
* server action before it calls ProjectBoardService.
|
|
43
|
+
*/
|
|
44
|
+
onmove: (intent: ProjectBoardMoveIntent) => void | Promise<void>;
|
|
45
|
+
/** Required reconciliation of controlled cards after every move attempt. */
|
|
46
|
+
onrefresh: () => void | Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A controlled project board is movable only with an authoritative refresh. */
|
|
50
|
+
export type Props = ProjectBoardBaseProps &
|
|
51
|
+
(ReadOnlyProjectBoardProps | MovableProjectBoardProps);
|
|
52
|
+
|
|
53
|
+
let { projectId, statuses, items, onmove, onrefresh, onselect, label }: Props =
|
|
54
|
+
$props();
|
|
55
|
+
|
|
56
|
+
const { t } = useI18n();
|
|
57
|
+
const unmatchedColumnId = '__smrt_projects_unmatched__';
|
|
58
|
+
const statusByName = $derived(
|
|
59
|
+
new Map(statuses.map((status) => [status.name, status])),
|
|
60
|
+
);
|
|
61
|
+
const hasUnmatchedItems = $derived(
|
|
62
|
+
items.some((item) => !statusByName.has(item.status ?? '')),
|
|
63
|
+
);
|
|
64
|
+
const movementEnabled = $derived(
|
|
65
|
+
onmove !== undefined && onrefresh !== undefined,
|
|
66
|
+
);
|
|
67
|
+
const columns = $derived<ProjectBoardColumn[]>([
|
|
68
|
+
...statuses.map((status) => ({
|
|
69
|
+
id: status.id,
|
|
70
|
+
label: status.name,
|
|
71
|
+
status: status.name,
|
|
72
|
+
})),
|
|
73
|
+
...(hasUnmatchedItems
|
|
74
|
+
? [
|
|
75
|
+
{
|
|
76
|
+
id: unmatchedColumnId,
|
|
77
|
+
label: t(M['projects.project_board.unassigned']),
|
|
78
|
+
disabled: true,
|
|
79
|
+
},
|
|
80
|
+
]
|
|
81
|
+
: []),
|
|
82
|
+
]);
|
|
83
|
+
const cards = $derived<ProjectBoardCard[]>(
|
|
84
|
+
items.map((item) => ({
|
|
85
|
+
id: item.id,
|
|
86
|
+
item,
|
|
87
|
+
columnId: statusByName.has(item.status ?? '')
|
|
88
|
+
? (statusByName.get(item.status ?? '')?.id ?? unmatchedColumnId)
|
|
89
|
+
: unmatchedColumnId,
|
|
90
|
+
})),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
async function refresh(): Promise<void> {
|
|
94
|
+
if (!onrefresh) throw new Error('Project board refresh is required.');
|
|
95
|
+
await onrefresh();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function move(
|
|
99
|
+
intent: BoardMoveIntent<ProjectBoardCard, ProjectBoardColumn>,
|
|
100
|
+
): Promise<void> {
|
|
101
|
+
const target = columns.find((column) => column.id === intent.target.columnId);
|
|
102
|
+
if (!target?.status || target.disabled || !onmove || !onrefresh) {
|
|
103
|
+
throw new Error('Project board move failed.');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
await onmove({
|
|
108
|
+
projectId,
|
|
109
|
+
itemId: intent.card.item.id,
|
|
110
|
+
status: target.status,
|
|
111
|
+
});
|
|
112
|
+
await refresh();
|
|
113
|
+
} catch {
|
|
114
|
+
// Controlled cards never become local truth. Re-fetch even when the
|
|
115
|
+
// mutation fails so a provider-side partial success is corrected.
|
|
116
|
+
try {
|
|
117
|
+
await refresh();
|
|
118
|
+
} catch {
|
|
119
|
+
// The original operation's failure is intentionally not exposed to the
|
|
120
|
+
// browser; it may contain provider details.
|
|
121
|
+
}
|
|
122
|
+
// Board owns the single live-region announcement and focus restoration.
|
|
123
|
+
throw new Error('Project board move failed.');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function cardLabel(card: ProjectBoardCard): string {
|
|
128
|
+
return card.item.title ?? card.item.id;
|
|
129
|
+
}
|
|
130
|
+
</script>
|
|
131
|
+
|
|
132
|
+
<Board
|
|
133
|
+
{columns}
|
|
134
|
+
{cards}
|
|
135
|
+
label={label ?? t(M['projects.project_board.aria'])}
|
|
136
|
+
getCardColumnId={(card) => card.columnId}
|
|
137
|
+
setCardColumnId={(card, columnId) => ({ ...card, columnId })}
|
|
138
|
+
getCardLabel={cardLabel}
|
|
139
|
+
allowSameColumnReorder={false}
|
|
140
|
+
onmove={movementEnabled ? move : undefined}
|
|
141
|
+
onselect={(card) => onselect?.(card.item)}
|
|
142
|
+
>
|
|
143
|
+
{#snippet card({ card })}
|
|
144
|
+
<!-- raw-primitive-allow: each board card is a semantic project selection control -->
|
|
145
|
+
<strong>{card.item.title ?? card.item.id}</strong>
|
|
146
|
+
<small>{card.item.type}</small>
|
|
147
|
+
{#if card.item.assignees?.length}
|
|
148
|
+
<small>{card.item.assignees.join(', ')}</small>
|
|
149
|
+
{/if}
|
|
150
|
+
{/snippet}
|
|
151
|
+
</Board>
|
|
152
|
+
|
|
153
|
+
<style>
|
|
154
|
+
small {
|
|
155
|
+
color: var(--smrt-color-on-surface-variant);
|
|
156
|
+
}
|
|
157
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ProjectItem, ProjectStatus } from '../types.js';
|
|
2
|
+
import type { ProjectBoardMoveIntent } from './project-board-types.js';
|
|
3
|
+
interface ProjectBoardBaseProps {
|
|
4
|
+
/** Provider project identity, forwarded only to the injected move action. */
|
|
5
|
+
projectId: string;
|
|
6
|
+
/** Canonical, ordered provider statuses. Their supplied order is preserved. */
|
|
7
|
+
statuses: readonly ProjectStatus[];
|
|
8
|
+
/** Authoritative provider items; this adapter never reorders them locally. */
|
|
9
|
+
items: readonly ProjectItem[];
|
|
10
|
+
onselect?: (item: ProjectItem) => void;
|
|
11
|
+
label?: string;
|
|
12
|
+
}
|
|
13
|
+
interface ReadOnlyProjectBoardProps {
|
|
14
|
+
/** Omit both callbacks to expose a read-only controlled board. */
|
|
15
|
+
onmove?: never;
|
|
16
|
+
onrefresh?: never;
|
|
17
|
+
}
|
|
18
|
+
interface MovableProjectBoardProps {
|
|
19
|
+
/**
|
|
20
|
+
* Browser-safe mutation boundary. Its consumer attaches authorization in a
|
|
21
|
+
* server action before it calls ProjectBoardService.
|
|
22
|
+
*/
|
|
23
|
+
onmove: (intent: ProjectBoardMoveIntent) => void | Promise<void>;
|
|
24
|
+
/** Required reconciliation of controlled cards after every move attempt. */
|
|
25
|
+
onrefresh: () => void | Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
/** A controlled project board is movable only with an authoritative refresh. */
|
|
28
|
+
export type Props = ProjectBoardBaseProps & (ReadOnlyProjectBoardProps | MovableProjectBoardProps);
|
|
29
|
+
declare const ProjectBoard: import("svelte").Component<Props, {}, "">;
|
|
30
|
+
type ProjectBoard = ReturnType<typeof ProjectBoard>;
|
|
31
|
+
export default ProjectBoard;
|
|
32
|
+
//# sourceMappingURL=ProjectBoard.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectBoard.svelte.d.ts","sourceRoot":"","sources":["../../src/svelte/ProjectBoard.svelte.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAYvE,UAAU,qBAAqB;IAC7B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,8EAA8E;IAC9E,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,yBAAyB;IACjC,kEAAkE;IAClE,MAAM,CAAC,EAAE,KAAK,CAAC;IACf,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB;AAED,UAAU,wBAAwB;IAChC;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,4EAA4E;IAC5E,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,gFAAgF;AAChF,MAAM,MAAM,KAAK,GAAG,qBAAqB,GACvC,CAAC,yBAAyB,GAAG,wBAAwB,CAAC,CAAC;AA4GzD,QAAA,MAAM,YAAY,2CAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { expectNoA11yViolations, fireEvent, render, screen, userEvent, } from '@happyvertical/smrt-vitest/svelte';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import ProjectBoard from '../../ProjectBoard.svelte';
|
|
5
|
+
const statuses = [
|
|
6
|
+
{ id: 'todo', name: 'Todo', order: 0 },
|
|
7
|
+
{ id: 'done', name: 'Done', order: 1 },
|
|
8
|
+
];
|
|
9
|
+
const items = [
|
|
10
|
+
{
|
|
11
|
+
id: 'item-1',
|
|
12
|
+
contentId: 'content-1',
|
|
13
|
+
title: 'Publish release notes',
|
|
14
|
+
status: 'Todo',
|
|
15
|
+
fields: {},
|
|
16
|
+
type: 'Issue',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'item-2',
|
|
20
|
+
contentId: 'content-2',
|
|
21
|
+
title: 'Stale provider status',
|
|
22
|
+
status: 'Provider-only',
|
|
23
|
+
fields: {},
|
|
24
|
+
type: 'DraftIssue',
|
|
25
|
+
},
|
|
26
|
+
];
|
|
27
|
+
const sameStatusItems = [
|
|
28
|
+
items[0],
|
|
29
|
+
{
|
|
30
|
+
id: 'item-3',
|
|
31
|
+
contentId: 'content-3',
|
|
32
|
+
title: 'Review release notes',
|
|
33
|
+
status: 'Todo',
|
|
34
|
+
fields: {},
|
|
35
|
+
type: 'Issue',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
describe('ProjectBoard', () => {
|
|
39
|
+
it('does not persist same-status keyboard or pointer reordering', async () => {
|
|
40
|
+
const onmove = vi.fn().mockResolvedValue(undefined);
|
|
41
|
+
const onrefresh = vi.fn().mockResolvedValue(undefined);
|
|
42
|
+
render(ProjectBoard, {
|
|
43
|
+
props: {
|
|
44
|
+
projectId: 'project-1',
|
|
45
|
+
statuses,
|
|
46
|
+
items: sameStatusItems,
|
|
47
|
+
onmove,
|
|
48
|
+
onrefresh,
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
const first = screen.getByRole('button', {
|
|
52
|
+
name: /Publish release notes/,
|
|
53
|
+
});
|
|
54
|
+
const second = screen.getByRole('button', {
|
|
55
|
+
name: /Review release notes/,
|
|
56
|
+
});
|
|
57
|
+
first.focus();
|
|
58
|
+
await userEvent.keyboard(' ');
|
|
59
|
+
await userEvent.keyboard('{ArrowDown}');
|
|
60
|
+
await userEvent.keyboard(' ');
|
|
61
|
+
fireEvent.dragStart(first, { dataTransfer: { setData: vi.fn() } });
|
|
62
|
+
fireEvent.drop(second, { clientY: 999 });
|
|
63
|
+
expect(onmove).not.toHaveBeenCalled();
|
|
64
|
+
expect(onrefresh).not.toHaveBeenCalled();
|
|
65
|
+
expect(screen.queryByText(/Moved Publish release notes to Todo/i)).not.toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
it('stays read-only when a browser move callback has no authoritative refresh', async () => {
|
|
68
|
+
const onmove = vi.fn();
|
|
69
|
+
render(ProjectBoard, {
|
|
70
|
+
// Runtime callers can bypass the public discriminated callback contract.
|
|
71
|
+
// The adapter must still avoid a move that cannot reconcile its cards.
|
|
72
|
+
props: {
|
|
73
|
+
projectId: 'project-1',
|
|
74
|
+
statuses,
|
|
75
|
+
items: [items[0]],
|
|
76
|
+
onmove,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
const card = screen.getByRole('button', {
|
|
80
|
+
name: /Publish release notes/,
|
|
81
|
+
});
|
|
82
|
+
await userEvent.click(card);
|
|
83
|
+
card.focus();
|
|
84
|
+
await userEvent.keyboard(' ');
|
|
85
|
+
await userEvent.keyboard('{ArrowRight}');
|
|
86
|
+
await userEvent.keyboard(' ');
|
|
87
|
+
expect(onmove).not.toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
it('preserves supplied status order, keeps unmatched items visible, and sends a pure move intent', async () => {
|
|
90
|
+
const onmove = vi.fn().mockResolvedValue(undefined);
|
|
91
|
+
const onrefresh = vi.fn().mockResolvedValue(undefined);
|
|
92
|
+
const onselect = vi.fn();
|
|
93
|
+
const { container } = render(ProjectBoard, {
|
|
94
|
+
props: {
|
|
95
|
+
projectId: 'project-1',
|
|
96
|
+
statuses,
|
|
97
|
+
items,
|
|
98
|
+
onmove,
|
|
99
|
+
onrefresh,
|
|
100
|
+
onselect,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
const lanes = Array.from(container.querySelectorAll('.smrt-board__lane'));
|
|
104
|
+
expect(lanes.map((lane) => lane.getAttribute('aria-label'))).toEqual([
|
|
105
|
+
'Todo, 1 cards',
|
|
106
|
+
'Done, 0 cards',
|
|
107
|
+
'Unassigned, 1 cards',
|
|
108
|
+
]);
|
|
109
|
+
expect(screen.getByText('Stale provider status')).toBeVisible();
|
|
110
|
+
const card = screen.getByRole('button', {
|
|
111
|
+
name: /Publish release notes/,
|
|
112
|
+
});
|
|
113
|
+
await userEvent.click(card);
|
|
114
|
+
expect(onselect).toHaveBeenCalledWith(items[0]);
|
|
115
|
+
card.focus();
|
|
116
|
+
await userEvent.keyboard(' ');
|
|
117
|
+
await userEvent.keyboard('{ArrowRight}');
|
|
118
|
+
await userEvent.keyboard(' ');
|
|
119
|
+
await vi.waitFor(() => {
|
|
120
|
+
expect(onmove).toHaveBeenCalledWith({
|
|
121
|
+
projectId: 'project-1',
|
|
122
|
+
itemId: 'item-1',
|
|
123
|
+
status: 'Done',
|
|
124
|
+
});
|
|
125
|
+
expect(onrefresh).toHaveBeenCalledTimes(1);
|
|
126
|
+
});
|
|
127
|
+
await expectNoA11yViolations(container);
|
|
128
|
+
});
|
|
129
|
+
it('refreshes authoritative state and lets Board make one sanitized failure announcement', async () => {
|
|
130
|
+
const onmove = vi
|
|
131
|
+
.fn()
|
|
132
|
+
.mockRejectedValue(new Error('provider token detail'));
|
|
133
|
+
const onrefresh = vi.fn().mockResolvedValue(undefined);
|
|
134
|
+
render(ProjectBoard, {
|
|
135
|
+
props: {
|
|
136
|
+
projectId: 'project-1',
|
|
137
|
+
statuses,
|
|
138
|
+
items: [items[0]],
|
|
139
|
+
onmove,
|
|
140
|
+
onrefresh,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
const card = screen.getByRole('button', {
|
|
144
|
+
name: /Publish release notes/,
|
|
145
|
+
});
|
|
146
|
+
card.focus();
|
|
147
|
+
await userEvent.keyboard(' ');
|
|
148
|
+
await userEvent.keyboard('{ArrowRight}');
|
|
149
|
+
await userEvent.keyboard(' ');
|
|
150
|
+
await vi.waitFor(() => {
|
|
151
|
+
expect(onrefresh).toHaveBeenCalledTimes(1);
|
|
152
|
+
expect(screen.getByText('Could not move Publish release notes. The board was restored.')).toBeVisible();
|
|
153
|
+
expect(screen.getAllByText('Could not move Publish release notes. The board was restored.')).toHaveLength(1);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
package/dist/svelte/i18n.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export declare const M: {
|
|
|
11
11
|
readonly 'projects.development_request.list_aria': "projects.development_request.list_aria";
|
|
12
12
|
readonly 'projects.development_board.aria': "projects.development_board.aria";
|
|
13
13
|
readonly 'projects.development_board.empty': "projects.development_board.empty";
|
|
14
|
+
readonly 'projects.project_board.aria': "projects.project_board.aria";
|
|
15
|
+
readonly 'projects.project_board.unassigned': "projects.project_board.unassigned";
|
|
14
16
|
readonly 'projects.development_request.detail_aria': "projects.development_request.detail_aria";
|
|
15
17
|
readonly 'projects.development_request.status': "projects.development_request.status";
|
|
16
18
|
readonly 'projects.development_request.visibility': "projects.development_request.visibility";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,CAAC
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkDZ,CAAC"}
|
package/dist/svelte/i18n.js
CHANGED
|
@@ -12,6 +12,8 @@ export const M = defineMessages({
|
|
|
12
12
|
'projects.development_request.list_aria': 'Development requests',
|
|
13
13
|
'projects.development_board.aria': 'Development board',
|
|
14
14
|
'projects.development_board.empty': 'No visible development work',
|
|
15
|
+
'projects.project_board.aria': 'Project board',
|
|
16
|
+
'projects.project_board.unassigned': 'Unassigned',
|
|
15
17
|
'projects.development_request.detail_aria': 'Development request details',
|
|
16
18
|
'projects.development_request.status': 'Status',
|
|
17
19
|
'projects.development_request.visibility': 'Visibility',
|
package/dist/svelte/index.d.ts
CHANGED
|
@@ -21,8 +21,9 @@ import DeliveryStatus from './DeliveryStatus.svelte';
|
|
|
21
21
|
import DevelopmentBoard from './DevelopmentBoard.svelte';
|
|
22
22
|
import DevelopmentRequestDetail from './DevelopmentRequestDetail.svelte';
|
|
23
23
|
import PreviewApprovalPanel from './PreviewApprovalPanel.svelte';
|
|
24
|
+
import ProjectBoard from './ProjectBoard.svelte';
|
|
24
25
|
import ServiceEvidenceList from './ServiceEvidenceList.svelte';
|
|
25
|
-
export { ApprovalActions, AssistanceLauncher, BulkActions, DeliveryStatus, DevelopmentBoard, DevelopmentRequestDetail, DevelopmentRequestForm, DevelopmentRequestList, DurationDisplay, PreviewApprovalPanel, RejectDialog, ServiceEvidenceList, TimeEntryCard, TimeEntryList, TimeSummary, };
|
|
26
|
+
export { ApprovalActions, AssistanceLauncher, BulkActions, DeliveryStatus, DevelopmentBoard, DevelopmentRequestDetail, DevelopmentRequestForm, DevelopmentRequestList, DurationDisplay, PreviewApprovalPanel, ProjectBoard, RejectDialog, ServiceEvidenceList, TimeEntryCard, TimeEntryList, TimeSummary, };
|
|
26
27
|
export type ApprovalActionsProps = ComponentProps<typeof ApprovalActions>;
|
|
27
28
|
export type AssistanceLauncherProps = ComponentProps<typeof AssistanceLauncher>;
|
|
28
29
|
export type BulkActionsProps = ComponentProps<typeof BulkActions>;
|
|
@@ -34,10 +35,12 @@ export type DevelopmentRequestListProps = ComponentProps<typeof DevelopmentReque
|
|
|
34
35
|
export type DurationDisplayProps = ComponentProps<typeof DurationDisplay>;
|
|
35
36
|
export type RejectDialogProps = ComponentProps<typeof RejectDialog>;
|
|
36
37
|
export type PreviewApprovalPanelProps = ComponentProps<typeof PreviewApprovalPanel>;
|
|
38
|
+
export type ProjectBoardProps = ComponentProps<typeof ProjectBoard>;
|
|
37
39
|
export type ServiceEvidenceListProps = ComponentProps<typeof ServiceEvidenceList>;
|
|
38
40
|
export type TimeEntryCardProps = ComponentProps<typeof TimeEntryCard>;
|
|
39
41
|
export type TimeEntryListProps = ComponentProps<typeof TimeEntryList>;
|
|
40
42
|
export type TimeSummaryProps = ComponentProps<typeof TimeSummary>;
|
|
41
43
|
export * from './delivery-types.js';
|
|
44
|
+
export type { ProjectBoardMoveIntent } from './project-board-types.js';
|
|
42
45
|
export { type ApprovalStatus, type Currency, formatCurrency, formatDate, formatHours, formatHoursHHMM, statusColors, type TimeEntry, type TimeEntryStatus, } from './utils.js';
|
|
43
46
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C,OAAO,kBAAkB,MAAM,6BAA6B,CAAC;AAE7D,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAClE,OAAO,WAAW,MAAM,iCAAiC,CAAC;AAC1D,OAAO,sBAAsB,MAAM,4CAA4C,CAAC;AAChF,OAAO,sBAAsB,MAAM,4CAA4C,CAAC;AAChF,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAClE,OAAO,YAAY,MAAM,kCAAkC,CAAC;AAC5D,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAC9D,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAC9D,OAAO,WAAW,MAAM,iCAAiC,CAAC;AAC1D,OAAO,cAAc,MAAM,yBAAyB,CAAC;AACrD,OAAO,gBAAgB,MAAM,2BAA2B,CAAC;AACzD,OAAO,wBAAwB,MAAM,mCAAmC,CAAC;AACzE,OAAO,oBAAoB,MAAM,+BAA+B,CAAC;AACjE,OAAO,mBAAmB,MAAM,8BAA8B,CAAC;AAG/D,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,EACtB,eAAe,EACf,oBAAoB,EACpB,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,WAAW,GACZ,CAAC;AAGF,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1E,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChF,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,CAAC;AAClE,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAC;AACxE,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5E,MAAM,MAAM,2BAA2B,GAAG,cAAc,CACtD,OAAO,sBAAsB,CAC9B,CAAC;AACF,MAAM,MAAM,6BAA6B,GAAG,cAAc,CACxD,OAAO,wBAAwB,CAChC,CAAC;AACF,MAAM,MAAM,2BAA2B,GAAG,cAAc,CACtD,OAAO,sBAAsB,CAC9B,CAAC;AACF,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1E,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,YAAY,CAAC,CAAC;AACpE,MAAM,MAAM,yBAAyB,GAAG,cAAc,CACpD,OAAO,oBAAoB,CAC5B,CAAC;AACF,MAAM,MAAM,wBAAwB,GAAG,cAAc,CACnD,OAAO,mBAAmB,CAC3B,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC;AACtE,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC;AACtE,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,CAAC;AAElE,cAAc,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C,OAAO,kBAAkB,MAAM,6BAA6B,CAAC;AAE7D,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAClE,OAAO,WAAW,MAAM,iCAAiC,CAAC;AAC1D,OAAO,sBAAsB,MAAM,4CAA4C,CAAC;AAChF,OAAO,sBAAsB,MAAM,4CAA4C,CAAC;AAChF,OAAO,eAAe,MAAM,qCAAqC,CAAC;AAClE,OAAO,YAAY,MAAM,kCAAkC,CAAC;AAC5D,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAC9D,OAAO,aAAa,MAAM,mCAAmC,CAAC;AAC9D,OAAO,WAAW,MAAM,iCAAiC,CAAC;AAC1D,OAAO,cAAc,MAAM,yBAAyB,CAAC;AACrD,OAAO,gBAAgB,MAAM,2BAA2B,CAAC;AACzD,OAAO,wBAAwB,MAAM,mCAAmC,CAAC;AACzE,OAAO,oBAAoB,MAAM,+BAA+B,CAAC;AACjE,OAAO,YAAY,MAAM,uBAAuB,CAAC;AACjD,OAAO,mBAAmB,MAAM,8BAA8B,CAAC;AAG/D,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,wBAAwB,EACxB,sBAAsB,EACtB,sBAAsB,EACtB,eAAe,EACf,oBAAoB,EACpB,YAAY,EACZ,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,WAAW,GACZ,CAAC;AAGF,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1E,MAAM,MAAM,uBAAuB,GAAG,cAAc,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChF,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,CAAC;AAClE,MAAM,MAAM,mBAAmB,GAAG,cAAc,CAAC,OAAO,cAAc,CAAC,CAAC;AACxE,MAAM,MAAM,qBAAqB,GAAG,cAAc,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5E,MAAM,MAAM,2BAA2B,GAAG,cAAc,CACtD,OAAO,sBAAsB,CAC9B,CAAC;AACF,MAAM,MAAM,6BAA6B,GAAG,cAAc,CACxD,OAAO,wBAAwB,CAChC,CAAC;AACF,MAAM,MAAM,2BAA2B,GAAG,cAAc,CACtD,OAAO,sBAAsB,CAC9B,CAAC;AACF,MAAM,MAAM,oBAAoB,GAAG,cAAc,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1E,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,YAAY,CAAC,CAAC;AACpE,MAAM,MAAM,yBAAyB,GAAG,cAAc,CACpD,OAAO,oBAAoB,CAC5B,CAAC;AACF,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAC,OAAO,YAAY,CAAC,CAAC;AACpE,MAAM,MAAM,wBAAwB,GAAG,cAAc,CACnD,OAAO,mBAAmB,CAC3B,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC;AACtE,MAAM,MAAM,kBAAkB,GAAG,cAAc,CAAC,OAAO,aAAa,CAAC,CAAC;AACtE,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC,OAAO,WAAW,CAAC,CAAC;AAElE,cAAc,qBAAqB,CAAC;AACpC,YAAY,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEvE,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,QAAQ,EACb,cAAc,EACd,UAAU,EACV,WAAW,EACX,eAAe,EACf,YAAY,EACZ,KAAK,SAAS,EACd,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC"}
|
package/dist/svelte/index.js
CHANGED
|
@@ -23,15 +23,17 @@ import DeliveryStatus from './DeliveryStatus.svelte';
|
|
|
23
23
|
import DevelopmentBoard from './DevelopmentBoard.svelte';
|
|
24
24
|
import DevelopmentRequestDetail from './DevelopmentRequestDetail.svelte';
|
|
25
25
|
import PreviewApprovalPanel from './PreviewApprovalPanel.svelte';
|
|
26
|
+
import ProjectBoard from './ProjectBoard.svelte';
|
|
26
27
|
import ServiceEvidenceList from './ServiceEvidenceList.svelte';
|
|
27
28
|
// Export components
|
|
28
|
-
export { ApprovalActions, AssistanceLauncher, BulkActions, DeliveryStatus, DevelopmentBoard, DevelopmentRequestDetail, DevelopmentRequestForm, DevelopmentRequestList, DurationDisplay, PreviewApprovalPanel, RejectDialog, ServiceEvidenceList, TimeEntryCard, TimeEntryList, TimeSummary, };
|
|
29
|
+
export { ApprovalActions, AssistanceLauncher, BulkActions, DeliveryStatus, DevelopmentBoard, DevelopmentRequestDetail, DevelopmentRequestForm, DevelopmentRequestList, DurationDisplay, PreviewApprovalPanel, ProjectBoard, RejectDialog, ServiceEvidenceList, TimeEntryCard, TimeEntryList, TimeSummary, };
|
|
29
30
|
export * from './delivery-types.js';
|
|
30
31
|
// Export types and utilities
|
|
31
32
|
export { formatCurrency, formatDate, formatHours, formatHoursHHMM, statusColors, } from './utils.js';
|
|
32
33
|
// Auto-register with ModuleUIRegistry
|
|
33
34
|
ModuleUIRegistry.registerModule(PROJECTS_MODULE_META);
|
|
34
35
|
ModuleUIRegistry.register('@happyvertical/smrt-projects', 'development-board', DevelopmentBoard);
|
|
36
|
+
ModuleUIRegistry.register('@happyvertical/smrt-projects', 'project-board', ProjectBoard);
|
|
35
37
|
ModuleUIRegistry.register('@happyvertical/smrt-projects', 'delivery-status', DeliveryStatus);
|
|
36
38
|
ModuleUIRegistry.register('@happyvertical/smrt-projects', 'preview-approval', PreviewApprovalPanel);
|
|
37
39
|
ModuleUIRegistry.register('@happyvertical/smrt-projects', 'assistance-launcher', AssistanceLauncher);
|