@profpowell/vb-project-planning 0.2.1 → 0.2.3
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/README.md +10 -5
- package/dist/vb-project-planning.css +69 -3
- package/dist/vb-project-planning.js +3191 -2544
- package/dist/vb-project-planning.min.css +1 -1
- package/dist/vb-project-planning.min.js +221 -197
- package/package.json +7 -4
- package/src/lib/board-surface.js +158 -0
- package/src/lib/bundle-registry.js +2 -75
- package/src/lib/dates.js +125 -0
- package/src/lib/escape.js +34 -0
- package/src/lib/hash.js +16 -0
- package/src/lib/portrait-url.js +3 -29
- package/src/lib/src-loader.js +78 -0
- package/src/lib/vb-view-transition.js +7 -1
- package/src/utils/copy-init.js +56 -17
- package/src/web-components/_ux-base.js +409 -24
- package/src/web-components/adr-wc/README.md +18 -16
- package/src/web-components/adr-wc/api.json +7 -5
- package/src/web-components/adr-wc/logic.js +50 -136
- package/src/web-components/burndown-chart/api.json +8 -6
- package/src/web-components/burndown-chart/logic.js +75 -39
- package/src/web-components/empathy-map/api.json +13 -13
- package/src/web-components/empathy-map/logic.js +161 -168
- package/src/web-components/empathy-map/static.html +3 -3
- package/src/web-components/empathy-map/styles.js +1 -0
- package/src/web-components/gantt-chart/README.md +12 -6
- package/src/web-components/gantt-chart/api.json +23 -7
- package/src/web-components/gantt-chart/logic.js +456 -431
- package/src/web-components/gantt-chart/styles.css +14 -1
- package/src/web-components/impact-effort/api.json +9 -7
- package/src/web-components/impact-effort/logic.js +238 -209
- package/src/web-components/impact-effort/static.html +12 -4
- package/src/web-components/iron-triangle/_capacity.js +0 -18
- package/src/web-components/kanban-board/api.json +8 -6
- package/src/web-components/kanban-board/logic.js +289 -333
- package/src/web-components/product-roadmap/api.json +14 -13
- package/src/web-components/product-roadmap/logic.js +393 -246
- package/src/web-components/product-roadmap/styles.css +40 -0
- package/src/web-components/quadrant-grid/api.json +9 -4
- package/src/web-components/quadrant-grid/logic.js +80 -12
- package/src/web-components/quadrant-grid/styles.css +7 -0
- package/src/web-components/quality-target/_quality-utils.js +2 -46
- package/src/web-components/review-surface/README.md +9 -5
- package/src/web-components/review-surface/api.json +10 -8
- package/src/web-components/review-surface/logic.js +349 -218
- package/src/web-components/risk-register/api.json +1 -6
- package/src/web-components/risk-register/logic.js +71 -24
- package/src/web-components/risk-register/styles.css +9 -1
- package/src/web-components/story-map/api.json +8 -6
- package/src/web-components/story-map/logic.js +280 -281
- package/src/web-components/story-map/static.html +15 -5
- package/src/web-components/traceability-matrix/api.json +10 -3
- package/src/web-components/traceability-matrix/logic.js +112 -63
- package/src/web-components/traceability-matrix/styles.css +22 -1
- package/src/web-components/user-journey/api.json +25 -24
- package/src/web-components/user-journey/logic.js +76 -127
- package/src/web-components/user-journey/static.html +2 -2
- package/src/web-components/user-persona/api.json +15 -14
- package/src/web-components/user-persona/logic.js +71 -117
- package/src/web-components/user-persona/styles.js +1 -0
- package/src/web-components/user-story/api.json +13 -11
- package/src/web-components/user-story/logic.js +164 -195
- package/src/web-components/user-story/styles.js +28 -1
- package/src/web-components/work-item/README.md +3 -3
- package/src/web-components/work-item/api.json +13 -11
- package/src/web-components/work-item/logic.js +58 -154
|
@@ -5,26 +5,28 @@
|
|
|
5
5
|
"description": "General-purpose work unit card — tasks, bugs, chores, spikes, and features with slotted title, status, priority, assignee, and linked stories",
|
|
6
6
|
"htmlvalidate": { "flow": true, "permittedContent": ["@flow"] },
|
|
7
7
|
"attributes": [
|
|
8
|
-
{ "name": "item-id", "kind": "host-api", "purpose": "config", "type": "string", "description": "Unique work item identifier
|
|
9
|
-
{ "name": "type", "kind": "host-api", "purpose": "config", "type": "
|
|
10
|
-
{ "name": "priority", "kind": "host-api", "purpose": "config", "type": "
|
|
11
|
-
{ "name": "status", "kind": "host-api", "purpose": "config", "type": "
|
|
8
|
+
{ "name": "item-id", "kind": "host-api", "purpose": "config", "type": "string", "description": "Unique work item identifier; sets the HTML id on first connect when none is set" },
|
|
9
|
+
{ "name": "type", "kind": "host-api", "purpose": "config", "type": "enum", "values": ["task", "bug", "chore", "spike", "feature"], "default": "task", "description": "Item type" },
|
|
10
|
+
{ "name": "priority", "kind": "host-api", "purpose": "config", "type": "enum", "values": ["critical", "high", "medium", "low"], "default": "medium", "description": "Priority" },
|
|
11
|
+
{ "name": "status", "kind": "host-api", "purpose": "config", "type": "enum", "values": ["backlog", "to-do", "in-progress", "review", "done", "blocked"], "default": "backlog", "description": "Workflow status" },
|
|
12
12
|
{ "name": "estimate", "kind": "host-api", "purpose": "config", "type": "string", "description": "Effort estimate (points, hours, t-shirt size)" },
|
|
13
|
-
{ "name": "assignee", "kind": "host-api", "purpose": "config", "type": "string", "description": "Assignee name" },
|
|
13
|
+
{ "name": "assignee", "kind": "host-api", "purpose": "config", "type": "string", "description": "Assignee name (shown with initials avatar)" },
|
|
14
14
|
{ "name": "story-ids", "kind": "host-api", "purpose": "config", "type": "string", "description": "Comma-separated linked user-story IDs" },
|
|
15
|
-
{ "name": "detail", "kind": "host-api", "purpose": "config", "type": "
|
|
15
|
+
{ "name": "detail", "kind": "host-api", "purpose": "config", "type": "enum", "values": ["full", "compact", "minimal"], "default": "full", "description": "Detail level. minimal shows id, type and title only." },
|
|
16
16
|
{ "name": "compact", "kind": "host-api", "purpose": "config", "type": "boolean", "description": "Alias for detail=compact" },
|
|
17
|
-
{ "name": "src", "kind": "host-api", "purpose": "config", "type": "string", "description": "URL
|
|
17
|
+
{ "name": "src", "kind": "host-api", "purpose": "config", "type": "string", "description": "URL of a JSON work-item record. Fetched once per value; a newer value aborts the older request." }
|
|
18
18
|
],
|
|
19
19
|
"slots": [
|
|
20
|
-
{ "name": "title", "description": "Work item heading (e.g., <h3>)" },
|
|
20
|
+
{ "name": "title", "description": "Work item heading (e.g., <h3>). There is no title attribute." },
|
|
21
21
|
{ "name": "description", "description": "Work item description (e.g., <p>)" },
|
|
22
22
|
{ "name": "checklist", "description": "Checklist or subtasks (e.g., <ul>)" },
|
|
23
23
|
{ "name": "notes", "description": "Additional notes (e.g., <p>)" }
|
|
24
24
|
],
|
|
25
25
|
"events": [
|
|
26
|
-
{ "name": "work-item:ready", "detail": "{ itemId, title, type, priority, status }", "description": "
|
|
27
|
-
{ "name": "work-item:status", "detail": "{ status, itemId }", "description": "
|
|
28
|
-
{ "name": "work-item:priority", "detail": "{ priority, itemId }", "description": "
|
|
26
|
+
{ "name": "work-item:ready", "detail": "{ itemId, title, type, priority, status }", "description": "Once per element, after the first render at any detail level. With src, after the fetched data renders (not fired if the fetch fails). Not re-fired by attribute changes or moves." },
|
|
27
|
+
{ "name": "work-item:status", "detail": "{ status, itemId }", "description": "When updateStatus() is called with a known status" },
|
|
28
|
+
{ "name": "work-item:priority", "detail": "{ priority, itemId }", "description": "When updatePriority() is called with a known priority" },
|
|
29
|
+
{ "name": "work-item:upgraded", "detail": "", "description": "Once, in a microtask after first connect. Wait for this before assigning .data from a framework." },
|
|
30
|
+
{ "name": "work-item:data-changed", "detail": "{ data, source: 'property' }", "description": "After .data is assigned" }
|
|
29
31
|
]
|
|
30
32
|
}
|
|
@@ -22,22 +22,24 @@
|
|
|
22
22
|
* @attr {string} story-ids - Comma-separated linked story IDs
|
|
23
23
|
* @attr {string} detail - full | compact | minimal
|
|
24
24
|
* @attr {boolean} compact - Alias for detail="compact"
|
|
25
|
-
* @attr {string} src - URL to JSON data
|
|
25
|
+
* @attr {string} src - URL to JSON data (fetched once per value)
|
|
26
26
|
*
|
|
27
|
-
* @fires work-item:ready
|
|
28
|
-
* @fires work-item:status
|
|
29
|
-
* @fires work-item:priority
|
|
27
|
+
* @fires work-item:ready - Once, after the first render (after the src data renders when src is set)
|
|
28
|
+
* @fires work-item:status - When updateStatus() is called
|
|
29
|
+
* @fires work-item:priority - When updatePriority() is called
|
|
30
|
+
* @fires work-item:upgraded - Once, after first connect
|
|
31
|
+
* @fires work-item:data-changed - After .data assignment, detail: { data, source: 'property' }
|
|
30
32
|
*
|
|
31
33
|
* @example
|
|
32
34
|
* <work-item
|
|
33
35
|
* item-id="PROJ-42"
|
|
34
|
-
* title="Implement search API"
|
|
35
36
|
* type="task"
|
|
36
37
|
* status="in-progress"
|
|
37
38
|
* priority="high"
|
|
38
39
|
* assignee="Sarah Chen"
|
|
39
40
|
* estimate="5"
|
|
40
41
|
* story-ids="PROJ-101,PROJ-102">
|
|
42
|
+
* <h3 slot="title">Implement search API</h3>
|
|
41
43
|
* <p slot="description">Build the /search endpoint with filters.</p>
|
|
42
44
|
* <ul slot="checklist">
|
|
43
45
|
* <li>Schema design</li>
|
|
@@ -49,7 +51,10 @@
|
|
|
49
51
|
|
|
50
52
|
import { styles } from './styles.js';
|
|
51
53
|
import { registerComponent } from '../../lib/bundle-registry.js';
|
|
52
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
esc, initials, hashColor, lucideSvg, UXShadowElement,
|
|
56
|
+
PRIORITIES, STATUSES as BASE_STATUSES, lookup, fillSlot, splitList, detailLevel, markEmptySections,
|
|
57
|
+
} from '../_ux-base.js';
|
|
53
58
|
|
|
54
59
|
/* ── Type icons (Lucide SVG fragments) ─────────────── */
|
|
55
60
|
|
|
@@ -63,25 +68,16 @@ const TYPE_ICONS = {
|
|
|
63
68
|
|
|
64
69
|
/* ── Static lookups ────────────────────────────────── */
|
|
65
70
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
low: { label: 'Low', color: '#16a34a', bg: 'rgba(22, 163, 74, 0.1)' },
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const STATUSES = {
|
|
74
|
-
backlog: { label: 'Backlog', color: '#6b7280', bg: 'rgba(107, 114, 128, 0.1)' },
|
|
75
|
-
'to-do': { label: 'To Do', color: '#3b82f6', bg: 'rgba(59, 130, 246, 0.1)' },
|
|
76
|
-
'in-progress': { label: 'In Progress', color: '#8b5cf6', bg: 'rgba(139, 92, 246, 0.1)' },
|
|
77
|
-
review: { label: 'Review', color: '#f59e0b', bg: 'rgba(245, 158, 11, 0.1)' },
|
|
78
|
-
done: { label: 'Done', color: '#22c55e', bg: 'rgba(34, 197, 94, 0.1)' },
|
|
79
|
-
blocked: { label: 'Blocked', color: '#dc2626', bg: 'rgba(220, 38, 38, 0.1)' },
|
|
80
|
-
};
|
|
71
|
+
const STATUSES = Object.freeze({
|
|
72
|
+
...BASE_STATUSES,
|
|
73
|
+
blocked: { label: 'Blocked', color: '#dc2626', bg: 'rgba(220, 38, 38, 0.1)' },
|
|
74
|
+
});
|
|
81
75
|
|
|
82
76
|
/* ── Component ─────────────────────────────────────── */
|
|
83
77
|
|
|
84
|
-
class WorkItem extends
|
|
78
|
+
class WorkItem extends UXShadowElement {
|
|
79
|
+
static readyEvent = 'work-item:ready';
|
|
80
|
+
|
|
85
81
|
static get observedAttributes() {
|
|
86
82
|
return [
|
|
87
83
|
'item-id', 'type', 'priority', 'status',
|
|
@@ -89,77 +85,35 @@ class WorkItem extends HTMLElement {
|
|
|
89
85
|
];
|
|
90
86
|
}
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
constructor() {
|
|
95
|
-
super();
|
|
96
|
-
this.attachShadow({ mode: 'open' });
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/* ── Slot caching ───────────────────────────────── */
|
|
100
|
-
|
|
101
|
-
#cacheSlotValues() {
|
|
102
|
-
for (const child of [...this.children]) {
|
|
103
|
-
const slotName = child.getAttribute('slot');
|
|
104
|
-
if (slotName) {
|
|
105
|
-
this.#slotCache.set(slotName, child.textContent.trim());
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
_resolve(attr) {
|
|
111
|
-
return this.getAttribute(attr) || this.#slotCache.get(attr) || '';
|
|
112
|
-
}
|
|
88
|
+
/* ── Lifecycle (see UXShadowElement) ──────────── */
|
|
113
89
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
connectedCallback() {
|
|
117
|
-
this.#cacheSlotValues();
|
|
90
|
+
_setup() {
|
|
118
91
|
if (this.itemId && !this.id) this.id = this.itemId;
|
|
119
|
-
if (this.hasAttribute('src')) {
|
|
120
|
-
this._loadSrc(this.getAttribute('src'));
|
|
121
|
-
}
|
|
122
|
-
this.#render();
|
|
123
|
-
this.setAttribute('data-upgraded', '');
|
|
124
92
|
}
|
|
125
93
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
} else {
|
|
135
|
-
this.#render();
|
|
136
|
-
}
|
|
137
|
-
}
|
|
94
|
+
_readyDetail() {
|
|
95
|
+
return {
|
|
96
|
+
itemId: this.itemId,
|
|
97
|
+
title: this.itemTitle,
|
|
98
|
+
type: this.itemType,
|
|
99
|
+
priority: this.priority,
|
|
100
|
+
status: this.status,
|
|
101
|
+
};
|
|
138
102
|
}
|
|
139
103
|
|
|
140
104
|
/* ── Getters ───────────────────────────────────── */
|
|
141
105
|
|
|
142
106
|
get itemId() { return this.getAttribute('item-id') || ''; }
|
|
143
|
-
get itemTitle() {
|
|
144
|
-
const slotted = this.querySelector('[slot="title"]');
|
|
145
|
-
return slotted?.textContent?.trim() || this.#slotCache.get('title') || '';
|
|
146
|
-
}
|
|
107
|
+
get itemTitle() { return this._slotText('title'); }
|
|
147
108
|
get itemType() { return this.getAttribute('type') || 'task'; }
|
|
148
109
|
get priority() { return this.getAttribute('priority') || 'medium'; }
|
|
149
110
|
get status() { return this.getAttribute('status') || 'backlog'; }
|
|
150
111
|
get estimate() { return this.getAttribute('estimate') || ''; }
|
|
151
112
|
get assignee() { return this.getAttribute('assignee') || ''; }
|
|
152
113
|
|
|
153
|
-
get storyIds() {
|
|
154
|
-
const raw = this.getAttribute('story-ids') || '';
|
|
155
|
-
return raw ? raw.split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
156
|
-
}
|
|
114
|
+
get storyIds() { return splitList(this.getAttribute('story-ids')); }
|
|
157
115
|
|
|
158
|
-
get _detailLevel() {
|
|
159
|
-
if (this.getAttribute('detail')) return this.getAttribute('detail');
|
|
160
|
-
if (this.hasAttribute('compact')) return 'compact';
|
|
161
|
-
return 'full';
|
|
162
|
-
}
|
|
116
|
+
get _detailLevel() { return detailLevel(this); }
|
|
163
117
|
|
|
164
118
|
get _minimalLabel() {
|
|
165
119
|
return this.itemTitle || this.itemId || 'Work item';
|
|
@@ -168,7 +122,7 @@ class WorkItem extends HTMLElement {
|
|
|
168
122
|
/* ── Public API ────────────────────────────────── */
|
|
169
123
|
|
|
170
124
|
updateStatus(newStatus) {
|
|
171
|
-
if (STATUSES
|
|
125
|
+
if (Object.hasOwn(STATUSES, newStatus)) {
|
|
172
126
|
this.setAttribute('status', newStatus);
|
|
173
127
|
this.dispatchEvent(new CustomEvent('work-item:status', {
|
|
174
128
|
detail: { status: newStatus, itemId: this.itemId },
|
|
@@ -178,7 +132,7 @@ class WorkItem extends HTMLElement {
|
|
|
178
132
|
}
|
|
179
133
|
|
|
180
134
|
updatePriority(newPriority) {
|
|
181
|
-
if (PRIORITIES
|
|
135
|
+
if (Object.hasOwn(PRIORITIES, newPriority)) {
|
|
182
136
|
this.setAttribute('priority', newPriority);
|
|
183
137
|
this.dispatchEvent(new CustomEvent('work-item:priority', {
|
|
184
138
|
detail: { priority: newPriority, itemId: this.itemId },
|
|
@@ -213,18 +167,13 @@ class WorkItem extends HTMLElement {
|
|
|
213
167
|
* Idempotent for repeat calls.
|
|
214
168
|
*/
|
|
215
169
|
set data(value) {
|
|
216
|
-
|
|
217
|
-
this._applyData(value);
|
|
218
|
-
if (this.shadowRoot) this.#render();
|
|
219
|
-
this.dispatchEvent(new CustomEvent('work-item:data-changed', {
|
|
220
|
-
detail: { data: this.data, source: 'property' },
|
|
221
|
-
bubbles: true, composed: true,
|
|
222
|
-
}));
|
|
170
|
+
this._setData(value);
|
|
223
171
|
}
|
|
224
172
|
|
|
225
173
|
/**
|
|
226
174
|
* Apply a data record to attributes + slotted children. Used by both the
|
|
227
|
-
* .data setter and
|
|
175
|
+
* .data setter and the src loader. Slotted children are created only when
|
|
176
|
+
* absent (authored content wins). No render or event emit — caller decides.
|
|
228
177
|
* @param {Record<string, unknown>} data
|
|
229
178
|
*/
|
|
230
179
|
_applyData(data) {
|
|
@@ -239,66 +188,39 @@ class WorkItem extends HTMLElement {
|
|
|
239
188
|
this.setAttribute('story-ids', Array.isArray(data.storyIds) ? data.storyIds.join(',') : String(data.storyIds));
|
|
240
189
|
}
|
|
241
190
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
this.appendChild(el);
|
|
247
|
-
}
|
|
248
|
-
for (const key of ['description', 'notes']) {
|
|
249
|
-
if (data[key] && !this.querySelector(`[slot="${key}"]`)) {
|
|
250
|
-
const el = document.createElement('p');
|
|
251
|
-
el.slot = key;
|
|
252
|
-
el.textContent = String(data[key]);
|
|
253
|
-
this.appendChild(el);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
if (data.checklist && !this.querySelector('[slot="checklist"]')) {
|
|
257
|
-
const ul = document.createElement('ul');
|
|
258
|
-
ul.slot = 'checklist';
|
|
259
|
-
const items = Array.isArray(data.checklist) ? data.checklist : [data.checklist];
|
|
260
|
-
for (const item of items) {
|
|
261
|
-
const li = document.createElement('li');
|
|
262
|
-
li.textContent = String(item);
|
|
263
|
-
ul.appendChild(li);
|
|
264
|
-
}
|
|
265
|
-
this.appendChild(ul);
|
|
266
|
-
}
|
|
191
|
+
fillSlot(this, 'title', data.title, { tag: 'h3' });
|
|
192
|
+
fillSlot(this, 'description', data.description);
|
|
193
|
+
fillSlot(this, 'notes', data.notes);
|
|
194
|
+
fillSlot(this, 'checklist', data.checklist, { list: true });
|
|
267
195
|
}
|
|
268
196
|
|
|
269
|
-
/* ── JSON loading
|
|
197
|
+
/* ── JSON loading states ───────────────────────── */
|
|
270
198
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
this.#render();
|
|
281
|
-
} catch (err) {
|
|
282
|
-
root.innerHTML = `<style>${styles}</style>` +
|
|
283
|
-
`<div class="state-msg state-msg--error">Could not load: ${esc(/** @type {Error} */ (err).message)}</div>`;
|
|
284
|
-
}
|
|
199
|
+
_renderLoading() {
|
|
200
|
+
/** @type {ShadowRoot} */ (this.shadowRoot).innerHTML =
|
|
201
|
+
`<style>${styles}</style><div class="state-msg">Loading\u2026</div>`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** @param {Error} err */
|
|
205
|
+
_renderError(err) {
|
|
206
|
+
/** @type {ShadowRoot} */ (this.shadowRoot).innerHTML = `<style>${styles}</style>` +
|
|
207
|
+
`<div class="state-msg state-msg--error">Could not load: ${esc(err.message)}</div>`;
|
|
285
208
|
}
|
|
286
209
|
|
|
287
210
|
/* ── Render ────────────────────────────────────── */
|
|
288
211
|
|
|
289
|
-
|
|
212
|
+
_render() {
|
|
290
213
|
const root = /** @type {ShadowRoot} */ (this.shadowRoot);
|
|
291
|
-
const priorityInfo = PRIORITIES
|
|
292
|
-
const statusInfo = STATUSES
|
|
214
|
+
const priorityInfo = lookup(PRIORITIES, this.priority, 'medium');
|
|
215
|
+
const statusInfo = lookup(STATUSES, this.status, 'backlog');
|
|
293
216
|
const type = this.itemType;
|
|
294
|
-
const typeIcon = TYPE_ICONS
|
|
217
|
+
const typeIcon = lookup(TYPE_ICONS, type, 'task');
|
|
295
218
|
const level = this._detailLevel;
|
|
296
219
|
const ariaLabel = this.itemId ? `Work item: ${esc(this.itemId)}` : 'Work item';
|
|
297
220
|
|
|
298
221
|
if (level === 'minimal') {
|
|
299
222
|
root.innerHTML = `<style>${styles}</style>
|
|
300
|
-
<article class="wi-card wi-card--minimal" role="article" aria-label="${ariaLabel}"
|
|
301
|
-
tabindex="0">
|
|
223
|
+
<article class="wi-card wi-card--minimal" role="article" aria-label="${ariaLabel}">
|
|
302
224
|
<div class="wi-body">
|
|
303
225
|
<div class="wi-meta">
|
|
304
226
|
${this.itemId ? `<span class="wi-id">${esc(this.itemId)}</span>` : ''}
|
|
@@ -312,6 +234,7 @@ class WorkItem extends HTMLElement {
|
|
|
312
234
|
return;
|
|
313
235
|
}
|
|
314
236
|
|
|
237
|
+
|
|
315
238
|
root.innerHTML = `<style>${styles}</style>
|
|
316
239
|
<article class="wi-card wi-card--${level}" role="article" aria-label="${ariaLabel}">
|
|
317
240
|
|
|
@@ -334,7 +257,7 @@ class WorkItem extends HTMLElement {
|
|
|
334
257
|
|
|
335
258
|
${this.assignee ? `
|
|
336
259
|
<div class="wi-assignee">
|
|
337
|
-
<span class="wi-assignee__avatar" style="background:${hashColor(this.assignee)}">${initials(this.assignee)}</span>
|
|
260
|
+
<span class="wi-assignee__avatar" style="background:${hashColor(this.assignee)}">${esc(initials(this.assignee))}</span>
|
|
338
261
|
${esc(this.assignee)}
|
|
339
262
|
</div>` : ''}
|
|
340
263
|
|
|
@@ -386,26 +309,7 @@ class WorkItem extends HTMLElement {
|
|
|
386
309
|
|
|
387
310
|
</article>`;
|
|
388
311
|
|
|
389
|
-
|
|
390
|
-
if (level === 'compact') {
|
|
391
|
-
for (const section of root.querySelectorAll('.wi-section')) {
|
|
392
|
-
const slot = section.querySelector('slot');
|
|
393
|
-
if (slot && slot.assignedNodes().length === 0) {
|
|
394
|
-
section.setAttribute('data-empty', '');
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
this.dispatchEvent(new CustomEvent('work-item:ready', {
|
|
400
|
-
detail: {
|
|
401
|
-
itemId: this.itemId,
|
|
402
|
-
title: this.itemTitle,
|
|
403
|
-
type,
|
|
404
|
-
priority: this.priority,
|
|
405
|
-
status: this.status,
|
|
406
|
-
},
|
|
407
|
-
bubbles: true, composed: true,
|
|
408
|
-
}));
|
|
312
|
+
if (level === 'compact') markEmptySections(root, '.wi-section');
|
|
409
313
|
}
|
|
410
314
|
}
|
|
411
315
|
|