@profpowell/vb-project-planning 0.1.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/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/vb-project-planning.css +1603 -0
- package/dist/vb-project-planning.js +11138 -0
- package/dist/vb-project-planning.min.css +1 -0
- package/dist/vb-project-planning.min.js +3312 -0
- package/package.json +80 -0
- package/src/index.css +14 -0
- package/src/index.js +26 -0
- package/src/lib/bundle-registry.js +156 -0
- package/src/lib/diff-by-key.js +86 -0
- package/src/lib/portrait-url.js +87 -0
- package/src/lib/vb-collection.js +122 -0
- package/src/lib/vb-element.js +96 -0
- package/src/lib/vb-store.js +200 -0
- package/src/lib/vb-view-transition.js +44 -0
- package/src/utils/copy-init.js +287 -0
- package/src/web-components/_ux-base.js +106 -0
- package/src/web-components/adr-wc/README.md +77 -0
- package/src/web-components/adr-wc/api.json +26 -0
- package/src/web-components/adr-wc/logic.js +376 -0
- package/src/web-components/adr-wc/static.html +17 -0
- package/src/web-components/adr-wc/styles.js +338 -0
- package/src/web-components/burndown-chart/api.json +74 -0
- package/src/web-components/burndown-chart/logic.js +198 -0
- package/src/web-components/burndown-chart/static.html +31 -0
- package/src/web-components/burndown-chart/styles.css +59 -0
- package/src/web-components/capacity-plan/_capacity-utils.js +56 -0
- package/src/web-components/capacity-plan/api.json +20 -0
- package/src/web-components/capacity-plan/logic.js +222 -0
- package/src/web-components/capacity-plan/static.html +24 -0
- package/src/web-components/capacity-plan/styles.css +84 -0
- package/src/web-components/empathy-map/api.json +31 -0
- package/src/web-components/empathy-map/logic.js +502 -0
- package/src/web-components/empathy-map/static.html +32 -0
- package/src/web-components/empathy-map/styles.js +534 -0
- package/src/web-components/gantt-chart/README.md +91 -0
- package/src/web-components/gantt-chart/api.json +20 -0
- package/src/web-components/gantt-chart/logic.js +812 -0
- package/src/web-components/gantt-chart/static.html +60 -0
- package/src/web-components/gantt-chart/styles.css +391 -0
- package/src/web-components/impact-effort/api.json +21 -0
- package/src/web-components/impact-effort/logic.js +392 -0
- package/src/web-components/impact-effort/static.html +23 -0
- package/src/web-components/impact-effort/styles.css +193 -0
- package/src/web-components/iron-triangle/_capacity.js +83 -0
- package/src/web-components/iron-triangle/_triangle-geometry.js +322 -0
- package/src/web-components/iron-triangle/api.json +26 -0
- package/src/web-components/iron-triangle/logic.js +474 -0
- package/src/web-components/iron-triangle/static.html +28 -0
- package/src/web-components/iron-triangle/styles.css +158 -0
- package/src/web-components/kanban-board/api.json +24 -0
- package/src/web-components/kanban-board/logic.js +698 -0
- package/src/web-components/kanban-board/static.html +20 -0
- package/src/web-components/kanban-board/styles.css +242 -0
- package/src/web-components/product-roadmap/api.json +118 -0
- package/src/web-components/product-roadmap/logic.js +460 -0
- package/src/web-components/product-roadmap/static.html +44 -0
- package/src/web-components/product-roadmap/styles.css +158 -0
- package/src/web-components/quadrant-grid/api.json +125 -0
- package/src/web-components/quadrant-grid/logic.js +397 -0
- package/src/web-components/quadrant-grid/static.html +49 -0
- package/src/web-components/quadrant-grid/styles.css +184 -0
- package/src/web-components/quality-target/_quality-utils.js +460 -0
- package/src/web-components/quality-target/api.json +30 -0
- package/src/web-components/quality-target/logic.js +516 -0
- package/src/web-components/quality-target/static.html +22 -0
- package/src/web-components/quality-target/styles.css +203 -0
- package/src/web-components/requirement-card/api.json +25 -0
- package/src/web-components/requirement-card/logic.js +166 -0
- package/src/web-components/requirement-card/static.html +36 -0
- package/src/web-components/requirement-card/styles.js +114 -0
- package/src/web-components/review-surface/README.md +178 -0
- package/src/web-components/review-surface/api.json +30 -0
- package/src/web-components/review-surface/logic.js +807 -0
- package/src/web-components/review-surface/static.html +33 -0
- package/src/web-components/review-surface/styles.js +459 -0
- package/src/web-components/risk-register/api.json +47 -0
- package/src/web-components/risk-register/logic.js +180 -0
- package/src/web-components/risk-register/static.html +46 -0
- package/src/web-components/risk-register/styles.css +37 -0
- package/src/web-components/story-map/api.json +22 -0
- package/src/web-components/story-map/logic.js +445 -0
- package/src/web-components/story-map/static.html +35 -0
- package/src/web-components/story-map/styles.css +169 -0
- package/src/web-components/traceability-matrix/api.json +84 -0
- package/src/web-components/traceability-matrix/logic.js +243 -0
- package/src/web-components/traceability-matrix/static.html +41 -0
- package/src/web-components/traceability-matrix/styles.css +94 -0
- package/src/web-components/user-journey/api.json +84 -0
- package/src/web-components/user-journey/logic.js +368 -0
- package/src/web-components/user-journey/static.html +14 -0
- package/src/web-components/user-journey/styles.js +317 -0
- package/src/web-components/user-persona/api.json +35 -0
- package/src/web-components/user-persona/logic.js +502 -0
- package/src/web-components/user-persona/static.html +20 -0
- package/src/web-components/user-persona/styles.js +299 -0
- package/src/web-components/user-story/api.json +35 -0
- package/src/web-components/user-story/logic.js +567 -0
- package/src/web-components/user-story/static.html +27 -0
- package/src/web-components/user-story/styles.js +332 -0
- package/src/web-components/work-item/README.md +85 -0
- package/src/web-components/work-item/api.json +30 -0
- package/src/web-components/work-item/logic.js +414 -0
- package/src/web-components/work-item/static.html +24 -0
- package/src/web-components/work-item/styles.js +376 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../../schemas/api.schema.json",
|
|
3
|
+
"element": "traceability-matrix",
|
|
4
|
+
"type": "web-component",
|
|
5
|
+
"description": "Auto-discovers a 2D cross-reference grid from page elements (RTM); composes <data-table> for sortable + heatmap-able presentation",
|
|
6
|
+
"attributes": [
|
|
7
|
+
{
|
|
8
|
+
"name": "rows",
|
|
9
|
+
"kind": "host-api",
|
|
10
|
+
"purpose": "config",
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "CSS selector for row source elements"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "cols",
|
|
16
|
+
"kind": "host-api",
|
|
17
|
+
"purpose": "config",
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "CSS selector for column source elements"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"name": "link-attr",
|
|
23
|
+
"kind": "host-api",
|
|
24
|
+
"purpose": "config",
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Attribute on the row element whose value(s) reference column ids; comma-separated values supported"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"name": "label",
|
|
30
|
+
"kind": "host-api",
|
|
31
|
+
"purpose": "config",
|
|
32
|
+
"type": "string",
|
|
33
|
+
"description": "Optional heading shown above the matrix"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"name": "row-label",
|
|
37
|
+
"kind": "host-api",
|
|
38
|
+
"purpose": "config",
|
|
39
|
+
"type": "string",
|
|
40
|
+
"description": "Header cell text for the row axis"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"name": "cell-mark",
|
|
44
|
+
"kind": "host-api",
|
|
45
|
+
"purpose": "config",
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "Glyph shown in linked cells (default: \u2713)"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "flag-orphans",
|
|
51
|
+
"kind": "host-api",
|
|
52
|
+
"purpose": "config",
|
|
53
|
+
"type": "boolean",
|
|
54
|
+
"description": "Mark empty rows/columns with data-orphan for CSS targeting"
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"childAttributes": [
|
|
58
|
+
{
|
|
59
|
+
"name": "data-matrix-label",
|
|
60
|
+
"on": "*",
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Override the label rendered for this element in matrices that include it"
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"events": [
|
|
66
|
+
{
|
|
67
|
+
"name": "traceability-matrix:ready",
|
|
68
|
+
"detail": "{ rowCount, colCount, linkCount }",
|
|
69
|
+
"description": "Fired after the matrix renders"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"name": "traceability-matrix:highlight",
|
|
73
|
+
"detail": "{ rowEl, colEl, on }",
|
|
74
|
+
"description": "Fired when a cell toggles chain highlight"
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
"structure": [],
|
|
78
|
+
"htmlvalidate": {
|
|
79
|
+
"flow": true,
|
|
80
|
+
"permittedContent": [
|
|
81
|
+
"@flow"
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* traceability-matrix: 2D cross-reference grid for planning artifacts
|
|
3
|
+
*
|
|
4
|
+
* Auto-discovers row and column source elements from the page and
|
|
5
|
+
* renders a <data-table> with checkmark intersections wherever a row
|
|
6
|
+
* element's `link-attr` references a column element's id. Surfaces
|
|
7
|
+
* the canonical Requirements Traceability Matrix (RTM) shape:
|
|
8
|
+
* rows × columns at-a-glance, with empty rows/columns flagged as
|
|
9
|
+
* orphans.
|
|
10
|
+
*
|
|
11
|
+
* Composes <data-table> for sortable / filterable / heatmap-able
|
|
12
|
+
* presentation. Adds two things <data-table> can't:
|
|
13
|
+
* 1. Auto-discovery — build the intersection table from selectors
|
|
14
|
+
* (rows="user-story" cols="user-persona" link-attr="persona-id")
|
|
15
|
+
* instead of forcing the author to hand-build every cell.
|
|
16
|
+
* 2. Chain highlight — clicking a cell toggles
|
|
17
|
+
* `data-state-highlighted` on the row AND column source elements
|
|
18
|
+
* page-wide, so authors can see the connection in surrounding
|
|
19
|
+
* components (cards, timelines, etc.).
|
|
20
|
+
*
|
|
21
|
+
* Cell semantics:
|
|
22
|
+
* ✓ — row's link-attr references this column's id
|
|
23
|
+
* (empty) — no link
|
|
24
|
+
*
|
|
25
|
+
* `link-attr` values are split on comma so attributes like
|
|
26
|
+
* `<work-item story-ids="s-1,s-2,s-3">` work without extra config.
|
|
27
|
+
*
|
|
28
|
+
* Labels: each source element's display label is, in order:
|
|
29
|
+
* 1. `data-matrix-label` attribute (author override)
|
|
30
|
+
* 2. element id
|
|
31
|
+
* 3. element textContent (trimmed, first line)
|
|
32
|
+
*
|
|
33
|
+
* @attr {string} rows - CSS selector for row source elements
|
|
34
|
+
* @attr {string} cols - CSS selector for column source elements
|
|
35
|
+
* @attr {string} link-attr - Attribute on the row element whose value(s) reference column ids
|
|
36
|
+
* @attr {string} label - Optional heading shown above the table
|
|
37
|
+
* @attr {string} row-label - Header cell text for the row axis (default: "")
|
|
38
|
+
* @attr {string} cell-mark - Glyph shown in linked cells (default: "✓")
|
|
39
|
+
* @attr {boolean} flag-orphans - When set, marks empty rows/cols with `data-orphan` for CSS targeting
|
|
40
|
+
*
|
|
41
|
+
* @fires traceability-matrix:ready - { rowCount, colCount, linkCount }
|
|
42
|
+
* @fires traceability-matrix:highlight - { rowEl, colEl, on } when a cell toggles
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
import { registerComponent } from '../../lib/bundle-registry.js';
|
|
46
|
+
import { VBElement } from '../../lib/vb-element.js';
|
|
47
|
+
|
|
48
|
+
class TraceabilityMatrix extends VBElement {
|
|
49
|
+
setup() {
|
|
50
|
+
const rowsSel = this.getAttribute('rows');
|
|
51
|
+
const colsSel = this.getAttribute('cols');
|
|
52
|
+
const linkAttr = this.getAttribute('link-attr');
|
|
53
|
+
if (!rowsSel || !colsSel || !linkAttr) {
|
|
54
|
+
console.warn('traceability-matrix: requires rows, cols, and link-attr attributes');
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* Defer one microtask so any sibling components (e.g. user-persona)
|
|
59
|
+
have finished assigning ids and slot content before we read them. */
|
|
60
|
+
queueMicrotask(() => this.#build(rowsSel, colsSel, linkAttr));
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#build(rowsSel, colsSel, linkAttr) {
|
|
65
|
+
const rowEls = [...document.querySelectorAll(rowsSel)].filter((el) => el !== this && !this.contains(el));
|
|
66
|
+
const colEls = [...document.querySelectorAll(colsSel)].filter((el) => el !== this && !this.contains(el));
|
|
67
|
+
|
|
68
|
+
if (!rowEls.length || !colEls.length) {
|
|
69
|
+
this.#renderEmpty();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const rowAxisLabel = this.getAttribute('row-label') || '';
|
|
74
|
+
const cellMark = this.getAttribute('cell-mark') || '✓';
|
|
75
|
+
const flagOrphans = this.hasAttribute('flag-orphans');
|
|
76
|
+
|
|
77
|
+
/* Compute links eagerly so we can flag orphans + emit a count. */
|
|
78
|
+
const links = []; // [{ rowEl, colEl }]
|
|
79
|
+
const rowHits = new WeakMap();
|
|
80
|
+
const colHits = new WeakMap();
|
|
81
|
+
for (const rowEl of rowEls) {
|
|
82
|
+
const refs = TraceabilityMatrix.#parseRefs(rowEl.getAttribute(linkAttr));
|
|
83
|
+
for (const ref of refs) {
|
|
84
|
+
const colEl = colEls.find((c) => c.id === ref);
|
|
85
|
+
if (!colEl) continue;
|
|
86
|
+
links.push({ rowEl, colEl });
|
|
87
|
+
rowHits.set(rowEl, (rowHits.get(rowEl) || 0) + 1);
|
|
88
|
+
colHits.set(colEl, (colHits.get(colEl) || 0) + 1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Build the data-table markup: header row of column labels,
|
|
93
|
+
one body row per row source. */
|
|
94
|
+
const wrapper = document.createElement('data-table');
|
|
95
|
+
const table = document.createElement('table');
|
|
96
|
+
const thead = document.createElement('thead');
|
|
97
|
+
const headerRow = document.createElement('tr');
|
|
98
|
+
|
|
99
|
+
const corner = document.createElement('th');
|
|
100
|
+
corner.setAttribute('data-sort', 'string');
|
|
101
|
+
corner.textContent = rowAxisLabel;
|
|
102
|
+
headerRow.appendChild(corner);
|
|
103
|
+
|
|
104
|
+
for (const colEl of colEls) {
|
|
105
|
+
const th = document.createElement('th');
|
|
106
|
+
th.setAttribute('data-sort', 'number');
|
|
107
|
+
th.textContent = TraceabilityMatrix.#labelOf(colEl);
|
|
108
|
+
th.dataset.colId = colEl.id || '';
|
|
109
|
+
if (flagOrphans && !colHits.get(colEl)) th.dataset.orphan = '';
|
|
110
|
+
headerRow.appendChild(th);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
thead.appendChild(headerRow);
|
|
114
|
+
table.appendChild(thead);
|
|
115
|
+
|
|
116
|
+
const tbody = document.createElement('tbody');
|
|
117
|
+
for (const rowEl of rowEls) {
|
|
118
|
+
const tr = document.createElement('tr');
|
|
119
|
+
tr.dataset.rowId = rowEl.id || '';
|
|
120
|
+
if (flagOrphans && !rowHits.get(rowEl)) tr.dataset.orphan = '';
|
|
121
|
+
|
|
122
|
+
const labelCell = document.createElement('th');
|
|
123
|
+
labelCell.scope = 'row';
|
|
124
|
+
labelCell.textContent = TraceabilityMatrix.#labelOf(rowEl);
|
|
125
|
+
tr.appendChild(labelCell);
|
|
126
|
+
|
|
127
|
+
const refs = TraceabilityMatrix.#parseRefs(rowEl.getAttribute(linkAttr));
|
|
128
|
+
for (const colEl of colEls) {
|
|
129
|
+
const td = document.createElement('td');
|
|
130
|
+
const matched = refs.includes(colEl.id);
|
|
131
|
+
td.setAttribute('data-sort-value', matched ? '1' : '0');
|
|
132
|
+
if (matched) {
|
|
133
|
+
td.dataset.linked = '';
|
|
134
|
+
td.textContent = cellMark;
|
|
135
|
+
td.setAttribute('aria-label', 'linked');
|
|
136
|
+
} else {
|
|
137
|
+
td.setAttribute('aria-label', 'not linked');
|
|
138
|
+
}
|
|
139
|
+
td.dataset.colId = colEl.id || '';
|
|
140
|
+
tr.appendChild(td);
|
|
141
|
+
}
|
|
142
|
+
tbody.appendChild(tr);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
table.appendChild(tbody);
|
|
146
|
+
wrapper.appendChild(table);
|
|
147
|
+
|
|
148
|
+
[...this.children].forEach((c) => { if (c.tagName !== 'TEMPLATE') c.remove(); });
|
|
149
|
+
|
|
150
|
+
const label = this.getAttribute('label');
|
|
151
|
+
if (label) {
|
|
152
|
+
const h = document.createElement('header');
|
|
153
|
+
h.className = 'tm-label';
|
|
154
|
+
h.textContent = label;
|
|
155
|
+
this.appendChild(h);
|
|
156
|
+
}
|
|
157
|
+
this.appendChild(wrapper);
|
|
158
|
+
|
|
159
|
+
/* Wire chain-highlight clicks via event delegation on the wrapper.
|
|
160
|
+
Any <td>/<th> click highlights its row + column source elements. */
|
|
161
|
+
this.#wrapper = wrapper;
|
|
162
|
+
this.#rowsByDom = new Map(rowEls.map((el) => [el.id, el]));
|
|
163
|
+
this.#colsByDom = new Map(colEls.map((el) => [el.id, el]));
|
|
164
|
+
this.listen(wrapper, 'click', this.#onCellClick);
|
|
165
|
+
|
|
166
|
+
queueMicrotask(() => {
|
|
167
|
+
this.dispatchEvent(new CustomEvent('traceability-matrix:ready', {
|
|
168
|
+
bubbles: true,
|
|
169
|
+
detail: { rowCount: rowEls.length, colCount: colEls.length, linkCount: links.length },
|
|
170
|
+
}));
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#onCellClick = (e) => {
|
|
175
|
+
const cell = e.target.closest('td, th[scope="row"]');
|
|
176
|
+
if (!cell) return;
|
|
177
|
+
const tr = cell.closest('tr');
|
|
178
|
+
if (!tr) return;
|
|
179
|
+
const rowId = tr.dataset.rowId;
|
|
180
|
+
const colId = cell.dataset.colId;
|
|
181
|
+
const rowEl = rowId ? this.#rowsByDom.get(rowId) : null;
|
|
182
|
+
const colEl = colId ? this.#colsByDom.get(colId) : null;
|
|
183
|
+
|
|
184
|
+
/* Toggle: if this cell is already highlighted, clear; else
|
|
185
|
+
set fresh highlight on row + col source elements. */
|
|
186
|
+
const wasOn = cell.hasAttribute('data-state-highlighted');
|
|
187
|
+
this.#clearHighlight();
|
|
188
|
+
if (wasOn) {
|
|
189
|
+
this.dispatchEvent(new CustomEvent('traceability-matrix:highlight', {
|
|
190
|
+
bubbles: true, detail: { rowEl, colEl, on: false },
|
|
191
|
+
}));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
cell.setAttribute('data-state-highlighted', '');
|
|
195
|
+
if (rowEl) rowEl.setAttribute('data-state-highlighted', '');
|
|
196
|
+
if (colEl && colEl !== rowEl) colEl.setAttribute('data-state-highlighted', '');
|
|
197
|
+
/* Highlight the entire row in the matrix for visual scan. */
|
|
198
|
+
tr.setAttribute('data-state-highlighted', '');
|
|
199
|
+
|
|
200
|
+
this.dispatchEvent(new CustomEvent('traceability-matrix:highlight', {
|
|
201
|
+
bubbles: true, detail: { rowEl, colEl, on: true },
|
|
202
|
+
}));
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
#clearHighlight() {
|
|
206
|
+
if (!this.#wrapper) return;
|
|
207
|
+
this.#wrapper.querySelectorAll('[data-state-highlighted]').forEach((el) => el.removeAttribute('data-state-highlighted'));
|
|
208
|
+
document.querySelectorAll('[data-state-highlighted]').forEach((el) => {
|
|
209
|
+
if (this.#rowsByDom.has(el.id) || this.#colsByDom.has(el.id)) el.removeAttribute('data-state-highlighted');
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#renderEmpty() {
|
|
214
|
+
[...this.children].forEach((c) => { if (c.tagName !== 'TEMPLATE') c.remove(); });
|
|
215
|
+
const note = document.createElement('p');
|
|
216
|
+
note.className = 'tm-empty';
|
|
217
|
+
note.textContent = 'No matching elements found for this matrix.';
|
|
218
|
+
this.appendChild(note);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
static #parseRefs(raw) {
|
|
222
|
+
if (!raw) return [];
|
|
223
|
+
return raw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
static #labelOf(el) {
|
|
227
|
+
return el.getAttribute('data-matrix-label')
|
|
228
|
+
|| el.id
|
|
229
|
+
|| (el.textContent || '').trim().split('\n')[0].slice(0, 60)
|
|
230
|
+
|| '(unnamed)';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** @type {HTMLElement | null} */
|
|
234
|
+
#wrapper = null;
|
|
235
|
+
/** @type {Map<string, Element>} */
|
|
236
|
+
#rowsByDom = new Map();
|
|
237
|
+
/** @type {Map<string, Element>} */
|
|
238
|
+
#colsByDom = new Map();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
registerComponent('traceability-matrix', TraceabilityMatrix);
|
|
242
|
+
|
|
243
|
+
export { TraceabilityMatrix };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Traceability Matrix — Static Fallback</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { font-family: system-ui, sans-serif; padding: 2rem; }
|
|
9
|
+
table { border-collapse: collapse; }
|
|
10
|
+
th, td { padding: 0.25rem 0.75rem; border-block-end: 1px solid #ddd; text-align: center; }
|
|
11
|
+
th[scope="row"] { text-align: start; }
|
|
12
|
+
</style>
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<!--
|
|
16
|
+
Static fallback for <traceability-matrix>.
|
|
17
|
+
|
|
18
|
+
Upgrade relationship:
|
|
19
|
+
- Without JS: the matrix host is invisible (no auto-discovery
|
|
20
|
+
possible). Authors who need a no-JS RTM should write the table
|
|
21
|
+
directly inside <data-table>; the matrix component is purely
|
|
22
|
+
additive (auto-discovery + chain highlight).
|
|
23
|
+
- With JS: the component reads `rows` and `cols` selectors,
|
|
24
|
+
auto-discovers source elements page-wide, builds a <data-table>
|
|
25
|
+
with checkmark intersections, and wires click-to-highlight.
|
|
26
|
+
-->
|
|
27
|
+
|
|
28
|
+
<traceability-matrix
|
|
29
|
+
rows="user-story"
|
|
30
|
+
cols="user-persona"
|
|
31
|
+
link-attr="persona-id"
|
|
32
|
+
label="Story coverage"
|
|
33
|
+
flag-orphans>
|
|
34
|
+
</traceability-matrix>
|
|
35
|
+
|
|
36
|
+
<user-persona id="p-sarah" data-matrix-label="Sarah Chen">…</user-persona>
|
|
37
|
+
<user-persona id="p-alex" data-matrix-label="Alex Rivera">…</user-persona>
|
|
38
|
+
<user-story story-id="s-1" persona-id="p-sarah" data-matrix-label="Story 1">…</user-story>
|
|
39
|
+
<user-story story-id="s-2" persona-id="p-alex" data-matrix-label="Story 2">…</user-story>
|
|
40
|
+
</body>
|
|
41
|
+
</html>
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/* traceability-matrix: 2D cross-reference grid */
|
|
2
|
+
|
|
3
|
+
traceability-matrix:not(:defined) {
|
|
4
|
+
display: block;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
traceability-matrix {
|
|
8
|
+
display: block;
|
|
9
|
+
font-family: var(--font-sans, system-ui);
|
|
10
|
+
color: var(--color-text, #1a1a1a);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
@scope (traceability-matrix) {
|
|
14
|
+
.tm-label {
|
|
15
|
+
display: block;
|
|
16
|
+
font-size: var(--font-size-xs, 0.75rem);
|
|
17
|
+
font-weight: 700;
|
|
18
|
+
text-transform: uppercase;
|
|
19
|
+
letter-spacing: 0.06em;
|
|
20
|
+
color: var(--color-text-muted, #666);
|
|
21
|
+
margin-block-end: var(--size-s, 0.75rem);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.tm-empty {
|
|
25
|
+
color: var(--color-text-muted, #666);
|
|
26
|
+
font-style: italic;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Linked cells get a subtle accent background */
|
|
30
|
+
td[data-linked] {
|
|
31
|
+
background: oklch(94% 0.05 145 / 0.55);
|
|
32
|
+
text-align: center;
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
color: var(--color-accent, oklch(45% 0.18 260));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Empty cells stay plain — easier to scan for orphans */
|
|
38
|
+
td:not([data-linked]) {
|
|
39
|
+
color: var(--color-text-muted, #666);
|
|
40
|
+
text-align: center;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Row label header cell */
|
|
44
|
+
th[scope="row"] {
|
|
45
|
+
text-align: start;
|
|
46
|
+
font-weight: 600;
|
|
47
|
+
background: var(--color-surface-raised, oklch(96% 0 0));
|
|
48
|
+
white-space: nowrap;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Highlighted chain — both the row in the matrix and the source elements */
|
|
52
|
+
tr[data-state-highlighted] {
|
|
53
|
+
outline: 2px solid var(--color-accent, oklch(45% 0.18 260));
|
|
54
|
+
outline-offset: -2px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
td[data-state-highlighted],
|
|
58
|
+
th[data-state-highlighted] {
|
|
59
|
+
background: oklch(85% 0.12 145 / 0.7);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Orphan flagging (when host has flag-orphans) */
|
|
63
|
+
tr[data-orphan] th[scope="row"]::after,
|
|
64
|
+
th[data-orphan]::after {
|
|
65
|
+
content: " ⚠";
|
|
66
|
+
color: var(--color-warning, oklch(60% 0.15 60));
|
|
67
|
+
font-weight: 700;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
td, th {
|
|
71
|
+
padding: var(--size-2xs, 0.25rem) var(--size-s, 0.75rem);
|
|
72
|
+
border-block-end: var(--border-width-thin, 1px) solid var(--color-border, oklch(85% 0 0));
|
|
73
|
+
cursor: default;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
td[data-linked] {
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Source-element highlight when the matrix triggers a chain */
|
|
82
|
+
[data-state-highlighted]:not(traceability-matrix *) {
|
|
83
|
+
outline: 2px solid var(--color-accent, oklch(45% 0.18 260));
|
|
84
|
+
outline-offset: 2px;
|
|
85
|
+
scroll-margin-block: var(--size-l, 1.5rem);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@media print {
|
|
89
|
+
traceability-matrix td[data-state-highlighted],
|
|
90
|
+
traceability-matrix tr[data-state-highlighted] {
|
|
91
|
+
outline: none;
|
|
92
|
+
background: none;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../../schemas/api.schema.json",
|
|
3
|
+
"element": "user-journey",
|
|
4
|
+
"type": "web-component",
|
|
5
|
+
"description": "Visualises a multi-phase user journey with SVG emotion curve and structured breakdown grid",
|
|
6
|
+
"htmlvalidate": {
|
|
7
|
+
"flow": true,
|
|
8
|
+
"permittedContent": [
|
|
9
|
+
"@flow"
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"attributes": [
|
|
13
|
+
{
|
|
14
|
+
"name": "title",
|
|
15
|
+
"kind": "host-api",
|
|
16
|
+
"purpose": "config",
|
|
17
|
+
"type": "string",
|
|
18
|
+
"description": "Journey map title"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"name": "persona",
|
|
22
|
+
"kind": "host-api",
|
|
23
|
+
"purpose": "config",
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Persona name"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"name": "persona-id",
|
|
29
|
+
"kind": "host-api",
|
|
30
|
+
"purpose": "config",
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "Links to a user-persona element by id"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "summary",
|
|
36
|
+
"kind": "host-api",
|
|
37
|
+
"purpose": "config",
|
|
38
|
+
"type": "string",
|
|
39
|
+
"description": "One-line journey description"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "story-ids",
|
|
43
|
+
"kind": "host-api",
|
|
44
|
+
"purpose": "config",
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Comma-separated related story IDs"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "src",
|
|
50
|
+
"kind": "host-api",
|
|
51
|
+
"purpose": "config",
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "URL to JSON file containing phase data"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"name": "compact",
|
|
57
|
+
"kind": "host-api",
|
|
58
|
+
"purpose": "config",
|
|
59
|
+
"type": "boolean",
|
|
60
|
+
"description": "Reduced spacing variant"
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"slots": [
|
|
64
|
+
{
|
|
65
|
+
"name": "title",
|
|
66
|
+
"description": "Journey title (slot fallback for attribute)"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "persona",
|
|
70
|
+
"description": "Persona name (slot fallback for attribute)"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"name": "summary",
|
|
74
|
+
"description": "One-line description (slot fallback for attribute)"
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
"events": [
|
|
78
|
+
{
|
|
79
|
+
"name": "journey-ready",
|
|
80
|
+
"detail": "{ title, persona, phaseCount }",
|
|
81
|
+
"description": "Fired after render completes"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
}
|