@principal-ai/subsystems-react 0.36.2 → 0.37.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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/pierre/PierreWalkthroughCodeView.d.ts +19 -1
- package/dist/pierre/PierreWalkthroughCodeView.d.ts.map +1 -1
- package/dist/pierre/PierreWalkthroughCodeView.js +53 -2
- package/dist/pierre/PierreWalkthroughCodeView.js.map +1 -1
- package/dist/subsystem/ComponentDeclaration.d.ts +13 -1
- package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
- package/dist/subsystem/ComponentDeclaration.js +58 -7
- package/dist/subsystem/ComponentDeclaration.js.map +1 -1
- package/dist/subsystem/ConstructsCatalog.d.ts +6 -3
- package/dist/subsystem/ConstructsCatalog.d.ts.map +1 -1
- package/dist/subsystem/ConstructsCatalog.js +206 -217
- package/dist/subsystem/ConstructsCatalog.js.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.d.ts +10 -0
- package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
- package/dist/subsystem/SubsystemComponentGraph.js +108 -31
- package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
- package/dist/subsystem/SymbolInspectionCard.d.ts.map +1 -1
- package/dist/subsystem/SymbolInspectionCard.js +1 -1
- package/dist/subsystem/SymbolInspectionCard.js.map +1 -1
- package/dist/subsystem/model.d.ts.map +1 -1
- package/dist/subsystem/model.js +7 -1
- package/dist/subsystem/model.js.map +1 -1
- package/dist/utils/edgeLabel.d.ts +51 -0
- package/dist/utils/edgeLabel.d.ts.map +1 -0
- package/dist/utils/edgeLabel.js +81 -0
- package/dist/utils/edgeLabel.js.map +1 -0
- package/dist/utils/elkLayout.d.ts +6 -0
- package/dist/utils/elkLayout.d.ts.map +1 -1
- package/dist/utils/elkLayout.js +27 -6
- package/dist/utils/elkLayout.js.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +7 -0
- package/src/pierre/PierreWalkthroughCodeView.tsx +75 -1
- package/src/stories/Subsystem/ComponentGraph/Flows.stories.tsx +222 -0
- package/src/stories/Subsystem/ConstructsCatalog.stories.tsx +443 -0
- package/src/subsystem/ComponentDeclaration.tsx +100 -7
- package/src/subsystem/ConstructsCatalog.tsx +291 -323
- package/src/subsystem/SubsystemComponentGraph.tsx +173 -30
- package/src/subsystem/SymbolInspectionCard.tsx +10 -9
- package/src/subsystem/model.ts +7 -1
- package/src/utils/edgeLabel.test.ts +23 -0
- package/src/utils/edgeLabel.ts +95 -0
- package/src/utils/elkLayout.ts +41 -6
|
@@ -3,15 +3,18 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
3
3
|
* ConstructsCatalog — master/detail for a constructs-only subsystem
|
|
4
4
|
* (components, no topology or walkthrough edges).
|
|
5
5
|
*
|
|
6
|
-
* Left: the model's
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* Left: the model's files as a tree; clicking a file toggles its
|
|
7
|
+
* constructs. Right: signatures grouped by repo, and within a repo stacked
|
|
8
|
+
* per file under one combined file header carrying the path and a
|
|
9
|
+
* description toggle; a declaration with a known line labels just that
|
|
10
|
+
* construct's line in a file-like gutter. File opens still use the bottom
|
|
11
|
+
* FileDrawer when the host injects a viewer.
|
|
9
12
|
*/
|
|
10
13
|
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
11
14
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
12
15
|
import { IndustryMarkdownSlide } from 'themed-markdown';
|
|
13
|
-
import { ChevronDown, ChevronRight, FileText, Folder } from 'lucide-react';
|
|
14
|
-
import {
|
|
16
|
+
import { AlignLeft, ChevronDown, ChevronRight, FileText, Folder } from 'lucide-react';
|
|
17
|
+
import { formatPurl, } from './model';
|
|
15
18
|
import { ComponentDeclaration } from './ComponentDeclaration';
|
|
16
19
|
import { SubsystemDiagnosticToggle } from './DiagnosticToggle';
|
|
17
20
|
import { FileDrawer } from './FileDrawer';
|
|
@@ -81,6 +84,24 @@ function repoLogo(c) {
|
|
|
81
84
|
return `https://github.com/${m[1]}.png?size=40`;
|
|
82
85
|
return undefined;
|
|
83
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Split a repo's declarations into consecutive runs that share a `file`, so
|
|
89
|
+
* each run can render as one connected stack. Components with no `file` are
|
|
90
|
+
* never merged (they have no shared location to group under).
|
|
91
|
+
*/
|
|
92
|
+
function splitByFileRun(items) {
|
|
93
|
+
const runs = [];
|
|
94
|
+
for (const c of items) {
|
|
95
|
+
const prev = runs[runs.length - 1];
|
|
96
|
+
const key = c.file || `alias:${c.alias}`;
|
|
97
|
+
const prevKey = prev && prev[0] ? prev[0].file || `alias:${prev[0].alias}` : null;
|
|
98
|
+
if (prev && prevKey === key)
|
|
99
|
+
prev.push(c);
|
|
100
|
+
else
|
|
101
|
+
runs.push([c]);
|
|
102
|
+
}
|
|
103
|
+
return runs;
|
|
104
|
+
}
|
|
84
105
|
export function ConstructsCatalog({ components, onSelect, title, hideSidebar, description, diagnostic, sidebarExtra, sidebarAfterDescription, renderFileViewer, renderFileView, onFileSelect, componentVerification, onInspectSymbol, }) {
|
|
85
106
|
const { theme, mode } = useTheme();
|
|
86
107
|
const pierreTheme = resolvePierreSyntaxThemeName(mode);
|
|
@@ -104,27 +125,51 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
104
125
|
})
|
|
105
126
|
.map((x) => x.c);
|
|
106
127
|
}, [components]);
|
|
107
|
-
const [visibleAliases, setVisibleAliases] = useState(
|
|
108
|
-
const [focusedAlias, setFocusedAlias] = useState(
|
|
128
|
+
const [visibleAliases, setVisibleAliases] = useState([]);
|
|
129
|
+
const [focusedAlias, setFocusedAlias] = useState(null);
|
|
130
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
109
131
|
const [descriptionVisible, setDescriptionVisible] = useState(false);
|
|
110
132
|
const [descToggleHover, setDescToggleHover] = useState(false);
|
|
111
133
|
const [hoveredRow, setHoveredRow] = useState(null);
|
|
112
|
-
const [sidebarTab, setSidebarTab] = useState('files');
|
|
113
134
|
const [collapsedFolders, setCollapsedFolders] = useState(new Set());
|
|
135
|
+
// Files whose shared-header description toggle is expanded (keyed by file run).
|
|
136
|
+
const [openPurposes, setOpenPurposes] = useState(new Set());
|
|
114
137
|
const [drawer, setDrawer] = useState(null);
|
|
115
|
-
const focusedAliasRef = useRef(focusedAlias);
|
|
116
|
-
focusedAliasRef.current = focusedAlias;
|
|
117
138
|
const visibleSet = useMemo(() => new Set(visibleAliases), [visibleAliases]);
|
|
118
139
|
const visibleComponents = useMemo(() => orderedComponents.filter((c) => visibleSet.has(c.alias)), [orderedComponents, visibleSet]);
|
|
140
|
+
// When nothing is toggled on, the right pane falls back to a searchable list
|
|
141
|
+
// of every construct, rendered as if all were selected.
|
|
142
|
+
const searchActive = visibleComponents.length === 0;
|
|
143
|
+
const searchResults = useMemo(() => {
|
|
144
|
+
if (!searchActive)
|
|
145
|
+
return visibleComponents;
|
|
146
|
+
const q = searchQuery.trim().toLowerCase();
|
|
147
|
+
if (!q)
|
|
148
|
+
return orderedComponents;
|
|
149
|
+
return orderedComponents.filter((c) => {
|
|
150
|
+
const haystack = [
|
|
151
|
+
c.name,
|
|
152
|
+
c.symbol,
|
|
153
|
+
c.alias,
|
|
154
|
+
c.file,
|
|
155
|
+
c.construct,
|
|
156
|
+
c.stereotype,
|
|
157
|
+
c.purpose,
|
|
158
|
+
]
|
|
159
|
+
.filter(Boolean)
|
|
160
|
+
.join(' ')
|
|
161
|
+
.toLowerCase();
|
|
162
|
+
return haystack.includes(q);
|
|
163
|
+
});
|
|
164
|
+
}, [searchActive, searchQuery, orderedComponents, visibleComponents]);
|
|
119
165
|
const fileTree = useMemo(() => buildFileTree(orderedComponents), [orderedComponents]);
|
|
120
166
|
const hasFiles = useMemo(() => orderedComponents.some((c) => !!c.file), [orderedComponents]);
|
|
121
|
-
|
|
122
|
-
//
|
|
123
|
-
// renders once per group instead of on every card.
|
|
167
|
+
// Group the declarations on screen by purl so the repo/app identity
|
|
168
|
+
// (logo + name) renders once per group instead of on every card.
|
|
124
169
|
const repoGroups = useMemo(() => {
|
|
125
170
|
const groups = [];
|
|
126
171
|
const index = new Map();
|
|
127
|
-
for (const c of
|
|
172
|
+
for (const c of searchResults) {
|
|
128
173
|
const key = c.purl || c.alias;
|
|
129
174
|
let at = index.get(key);
|
|
130
175
|
if (at == null) {
|
|
@@ -137,12 +182,7 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
137
182
|
groups[at].items.push(c);
|
|
138
183
|
}
|
|
139
184
|
return groups;
|
|
140
|
-
}, [
|
|
141
|
-
useEffect(() => {
|
|
142
|
-
if (!focusedAlias)
|
|
143
|
-
return;
|
|
144
|
-
document.getElementById(`construct-${focusedAlias}`)?.scrollIntoView({ block: 'nearest' });
|
|
145
|
-
}, [focusedAlias]);
|
|
185
|
+
}, [searchResults]);
|
|
146
186
|
useEffect(() => {
|
|
147
187
|
const last = visibleAliases[visibleAliases.length - 1];
|
|
148
188
|
if (!last)
|
|
@@ -163,34 +203,17 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
163
203
|
.getElementById(`construct-signature-${match.alias}`)
|
|
164
204
|
?.scrollIntoView({ block: 'nearest' });
|
|
165
205
|
}, [orderedComponents, drawer]);
|
|
166
|
-
// Drop aliases that left the model;
|
|
206
|
+
// Drop aliases that left the model; an empty set is valid (search fallback).
|
|
167
207
|
useEffect(() => {
|
|
168
208
|
const aliases = new Set(orderedComponents.map((c) => c.alias));
|
|
169
|
-
setVisibleAliases((prev) =>
|
|
170
|
-
|
|
171
|
-
if (next.length > 0 || orderedComponents.length === 0)
|
|
172
|
-
return next;
|
|
173
|
-
return [orderedComponents[0].alias];
|
|
174
|
-
});
|
|
175
|
-
setFocusedAlias((prev) => {
|
|
176
|
-
if (prev && aliases.has(prev))
|
|
177
|
-
return prev;
|
|
178
|
-
return orderedComponents[0]?.alias ?? null;
|
|
179
|
-
});
|
|
209
|
+
setVisibleAliases((prev) => prev.filter((alias) => aliases.has(alias)));
|
|
210
|
+
setFocusedAlias((prev) => (prev && aliases.has(prev) ? prev : null));
|
|
180
211
|
}, [orderedComponents]);
|
|
181
|
-
const toggle = useCallback((alias) => {
|
|
182
|
-
setFocusedAlias(alias);
|
|
183
|
-
setVisibleAliases((prev) => prev.includes(alias) ? prev.filter((x) => x !== alias) : [...prev, alias]);
|
|
184
|
-
onSelect?.(alias);
|
|
185
|
-
}, [onSelect]);
|
|
186
212
|
const show = useCallback((alias) => {
|
|
187
213
|
setFocusedAlias(alias);
|
|
188
214
|
setVisibleAliases((prev) => (prev.includes(alias) ? prev : [...prev, alias]));
|
|
189
215
|
onSelect?.(alias);
|
|
190
216
|
}, [onSelect]);
|
|
191
|
-
const focusAt = useCallback((alias) => {
|
|
192
|
-
setFocusedAlias(alias);
|
|
193
|
-
}, []);
|
|
194
217
|
const toggleFolder = useCallback((path) => {
|
|
195
218
|
setCollapsedFolders((prev) => {
|
|
196
219
|
const next = new Set(prev);
|
|
@@ -201,8 +224,18 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
201
224
|
return next;
|
|
202
225
|
});
|
|
203
226
|
}, []);
|
|
227
|
+
const togglePurpose = useCallback((key) => {
|
|
228
|
+
setOpenPurposes((prev) => {
|
|
229
|
+
const next = new Set(prev);
|
|
230
|
+
if (next.has(key))
|
|
231
|
+
next.delete(key);
|
|
232
|
+
else
|
|
233
|
+
next.add(key);
|
|
234
|
+
return next;
|
|
235
|
+
});
|
|
236
|
+
}, []);
|
|
204
237
|
/** File-tree click: show every construct declared in the file, or hide them
|
|
205
|
-
* all when they're already shown —
|
|
238
|
+
* all when they're already shown — a bulk toggle of the file's constructs. */
|
|
206
239
|
const toggleFile = useCallback((comps) => {
|
|
207
240
|
const aliases = comps.map((c) => c.alias);
|
|
208
241
|
setVisibleAliases((prev) => {
|
|
@@ -307,8 +340,17 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
307
340
|
const comp = resolveRelated(components, ref);
|
|
308
341
|
if (!comp)
|
|
309
342
|
return;
|
|
343
|
+
// In the all-constructs search fallback nothing needs revealing — just
|
|
344
|
+
// surface the target. Otherwise add it to the visible set.
|
|
345
|
+
if (searchActive) {
|
|
346
|
+
setFocusedAlias(comp.alias);
|
|
347
|
+
document
|
|
348
|
+
.getElementById(`construct-signature-${comp.alias}`)
|
|
349
|
+
?.scrollIntoView({ block: 'nearest' });
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
310
352
|
show(comp.alias);
|
|
311
|
-
}, [components, show]);
|
|
353
|
+
}, [components, show, searchActive]);
|
|
312
354
|
const fileViewer = useMemo(() => {
|
|
313
355
|
if (renderFileViewer)
|
|
314
356
|
return renderFileViewer;
|
|
@@ -324,57 +366,14 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
324
366
|
const fileViewerRef = useRef(fileViewer);
|
|
325
367
|
fileViewerRef.current = fileViewer;
|
|
326
368
|
const renderDrawerContent = useCallback((file, opts) => fileViewerRef.current?.(file, opts) ?? null, []);
|
|
327
|
-
const moveFocus = useCallback((delta) => {
|
|
328
|
-
if (orderedComponents.length === 0)
|
|
329
|
-
return;
|
|
330
|
-
const idx = Math.max(0, orderedComponents.findIndex((c) => c.alias === focusedAliasRef.current));
|
|
331
|
-
const next = orderedComponents[(idx + delta + orderedComponents.length) % orderedComponents.length];
|
|
332
|
-
focusAt(next.alias);
|
|
333
|
-
}, [orderedComponents, focusAt]);
|
|
334
369
|
useEffect(() => {
|
|
335
370
|
const onKey = (e) => {
|
|
336
|
-
|
|
337
|
-
if (el &&
|
|
338
|
-
(el.tagName === 'INPUT' ||
|
|
339
|
-
el.tagName === 'TEXTAREA' ||
|
|
340
|
-
el.tagName === 'SELECT' ||
|
|
341
|
-
el.isContentEditable)) {
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
if (e.metaKey || e.ctrlKey || e.altKey)
|
|
345
|
-
return;
|
|
346
|
-
if (e.key === 'ArrowDown') {
|
|
347
|
-
e.preventDefault();
|
|
348
|
-
moveFocus(1);
|
|
349
|
-
}
|
|
350
|
-
else if (e.key === 'ArrowUp') {
|
|
351
|
-
e.preventDefault();
|
|
352
|
-
moveFocus(-1);
|
|
353
|
-
}
|
|
354
|
-
else if (e.key === 'Home') {
|
|
355
|
-
e.preventDefault();
|
|
356
|
-
if (orderedComponents[0])
|
|
357
|
-
focusAt(orderedComponents[0].alias);
|
|
358
|
-
}
|
|
359
|
-
else if (e.key === 'End') {
|
|
360
|
-
e.preventDefault();
|
|
361
|
-
const last = orderedComponents[orderedComponents.length - 1];
|
|
362
|
-
if (last)
|
|
363
|
-
focusAt(last.alias);
|
|
364
|
-
}
|
|
365
|
-
else if (e.key === ' ' || e.key === 'Enter') {
|
|
366
|
-
if (focusedAliasRef.current) {
|
|
367
|
-
e.preventDefault();
|
|
368
|
-
toggle(focusedAliasRef.current);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
else if (e.key === 'Escape' && drawer) {
|
|
371
|
+
if (e.key === 'Escape' && drawer)
|
|
372
372
|
setDrawer(null);
|
|
373
|
-
}
|
|
374
373
|
};
|
|
375
374
|
window.addEventListener('keydown', onKey);
|
|
376
375
|
return () => window.removeEventListener('keydown', onKey);
|
|
377
|
-
}, [
|
|
376
|
+
}, [drawer]);
|
|
378
377
|
const showChrome = !hideSidebar &&
|
|
379
378
|
!!(title || description || diagnostic || sidebarExtra || sidebarAfterDescription);
|
|
380
379
|
const showDesc = !!description && descriptionVisible;
|
|
@@ -428,125 +427,38 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
428
427
|
background: descriptionVisible || descToggleHover ? theme.colors.border : 'transparent',
|
|
429
428
|
color: descriptionVisible || descToggleHover ? theme.colors.text : muted,
|
|
430
429
|
cursor: 'pointer',
|
|
431
|
-
}, children: _jsx(FileText, { size: 14 }) }))] })] })), showDesc && (_jsx("div", { style: { fontSize: theme.fontSizes[0], lineHeight: 1.5 }, children: _jsx(IndustryMarkdownSlide, { content: description, slideIdPrefix: "constructs-desc", slideIndex: 0, isVisible: true, theme: theme, disableScroll: true, disableBasePadding: true, enableKeyboardScrolling: false, autoFocusOnVisible: false }) })), sidebarAfterDescription] })), _jsx("
|
|
432
|
-
display: 'flex',
|
|
433
|
-
width: '100%',
|
|
434
|
-
flexShrink: 0,
|
|
435
|
-
borderBottom: `1px solid ${theme.colors.border}`,
|
|
436
|
-
background: theme.colors.backgroundSecondary ?? theme.colors.background,
|
|
437
|
-
}, children: ['files', 'constructs'].map((tab) => {
|
|
438
|
-
if (tab === 'files' && !hasFiles)
|
|
439
|
-
return null;
|
|
440
|
-
const active = effectiveTab === tab;
|
|
441
|
-
return (_jsx("button", { type: "button", role: "tab", "aria-selected": active, onClick: () => setSidebarTab(tab), style: {
|
|
442
|
-
flex: 1,
|
|
443
|
-
minWidth: 0,
|
|
444
|
-
padding: '8px 8px',
|
|
445
|
-
border: 'none',
|
|
446
|
-
borderRadius: 0,
|
|
447
|
-
background: active
|
|
448
|
-
? theme.colors.background
|
|
449
|
-
: 'transparent',
|
|
450
|
-
color: active ? theme.colors.text : muted,
|
|
451
|
-
fontSize: theme.fontSizes[0],
|
|
452
|
-
fontFamily: theme.fonts.monospace,
|
|
453
|
-
letterSpacing: 0.4,
|
|
454
|
-
textTransform: 'uppercase',
|
|
455
|
-
cursor: 'pointer',
|
|
456
|
-
}, children: tab === 'constructs' ? 'Constructs' : 'Files' }, tab));
|
|
457
|
-
}) }), effectiveTab === 'constructs' ? (_jsx("ul", { role: "listbox", "aria-label": "Constructs", "aria-multiselectable": "true", "aria-activedescendant": focusedAlias ? `construct-${focusedAlias}` : undefined, style: {
|
|
458
|
-
listStyle: 'none',
|
|
459
|
-
margin: 0,
|
|
460
|
-
padding: '0 8px 12px',
|
|
461
|
-
overflowY: 'auto',
|
|
462
|
-
flex: 1,
|
|
463
|
-
minHeight: 0,
|
|
464
|
-
}, children: orderedComponents.map((c) => {
|
|
465
|
-
const visible = visibleSet.has(c.alias);
|
|
466
|
-
const focused = c.alias === focusedAlias;
|
|
467
|
-
const color = componentColor(c, pierreTheme);
|
|
468
|
-
const badgeColor = constructBadgeColor(c) ?? color;
|
|
469
|
-
const badgeLabel = constructBadgeLabel(c);
|
|
470
|
-
const displayName = deriveNameFromSymbol(c.symbol, c.construct, c.name, c.file, c.stereotype);
|
|
471
|
-
const rowOpen = drawer != null &&
|
|
472
|
-
drawer.startLine != null &&
|
|
473
|
-
!!c.file &&
|
|
474
|
-
c.file === drawer.file &&
|
|
475
|
-
c.declarationRef?.startLine === drawer.startLine;
|
|
476
|
-
const hovered = hoveredRow === c.alias;
|
|
477
|
-
return (_jsx("li", { role: "presentation", children: _jsxs("button", { type: "button", id: `construct-${c.alias}`, role: "option", "aria-selected": visible, "data-testid": `construct-row-${c.alias}`, onClick: () => toggle(c.alias), onMouseEnter: () => setHoveredRow(c.alias), onMouseLeave: () => setHoveredRow((h) => (h === c.alias ? null : h)), style: {
|
|
478
|
-
display: 'flex',
|
|
479
|
-
flexDirection: 'column',
|
|
480
|
-
alignItems: 'stretch',
|
|
481
|
-
gap: 2,
|
|
482
|
-
width: '100%',
|
|
483
|
-
margin: '2px 0',
|
|
484
|
-
padding: '8px 10px 8px 12px',
|
|
485
|
-
textAlign: 'left',
|
|
486
|
-
border: 'none',
|
|
487
|
-
borderRadius: 6,
|
|
488
|
-
background: hovered
|
|
489
|
-
? theme.colors.border
|
|
490
|
-
: visible
|
|
491
|
-
? theme.colors.background
|
|
492
|
-
: 'transparent',
|
|
493
|
-
cursor: 'pointer',
|
|
494
|
-
transition: 'background 100ms ease, opacity 100ms ease',
|
|
495
|
-
boxShadow: [
|
|
496
|
-
visible ? `inset 0 0 0 1px ${theme.colors.border}` : null,
|
|
497
|
-
rowOpen ? `inset 2px 0 0 ${accentColor}` : null,
|
|
498
|
-
]
|
|
499
|
-
.filter(Boolean)
|
|
500
|
-
.join(', ') || undefined,
|
|
501
|
-
outline: focused && !visible ? `1px dotted ${muted}` : undefined,
|
|
502
|
-
outlineOffset: -1,
|
|
503
|
-
opacity: visible ? 1 : hovered ? 1 : 0.72,
|
|
504
|
-
}, children: [_jsx("span", { style: {
|
|
505
|
-
display: 'flex',
|
|
506
|
-
alignItems: 'baseline',
|
|
507
|
-
gap: 8,
|
|
508
|
-
minWidth: 0,
|
|
509
|
-
}, children: _jsx("span", { style: {
|
|
510
|
-
flex: 1,
|
|
511
|
-
minWidth: 0,
|
|
512
|
-
overflow: 'hidden',
|
|
513
|
-
textOverflow: 'ellipsis',
|
|
514
|
-
whiteSpace: 'nowrap',
|
|
515
|
-
fontFamily: theme.fonts.monospace,
|
|
516
|
-
fontSize: theme.fontSizes[1],
|
|
517
|
-
color,
|
|
518
|
-
fontWeight: visible ? 600 : 500,
|
|
519
|
-
}, children: displayName }) }), badgeLabel && (_jsx("span", { style: {
|
|
520
|
-
display: 'flex',
|
|
521
|
-
alignItems: 'center',
|
|
522
|
-
minWidth: 0,
|
|
523
|
-
}, children: _jsx("span", { style: {
|
|
524
|
-
flex: 1,
|
|
525
|
-
minWidth: 0,
|
|
526
|
-
overflow: 'hidden',
|
|
527
|
-
textOverflow: 'ellipsis',
|
|
528
|
-
whiteSpace: 'nowrap',
|
|
529
|
-
fontFamily: theme.fonts.monospace,
|
|
530
|
-
fontSize: theme.fontSizes[0],
|
|
531
|
-
letterSpacing: 0.4,
|
|
532
|
-
textTransform: 'uppercase',
|
|
533
|
-
color: badgeColor,
|
|
534
|
-
}, children: badgeLabel }) }))] }) }, c.alias));
|
|
535
|
-
}) })) : (_jsx("ul", { role: "tree", "aria-label": "Files", style: {
|
|
430
|
+
}, children: _jsx(FileText, { size: 14 }) }))] })] })), showDesc && (_jsx("div", { style: { fontSize: theme.fontSizes[0], lineHeight: 1.5 }, children: _jsx(IndustryMarkdownSlide, { content: description, slideIdPrefix: "constructs-desc", slideIndex: 0, isVisible: true, theme: theme, disableScroll: true, disableBasePadding: true, enableKeyboardScrolling: false, autoFocusOnVisible: false }) })), sidebarAfterDescription] })), hasFiles ? (_jsx("ul", { role: "tree", "aria-label": "Files", style: {
|
|
536
431
|
listStyle: 'none',
|
|
537
432
|
margin: 0,
|
|
538
433
|
padding: '6px 8px 12px',
|
|
539
434
|
overflowY: 'auto',
|
|
540
435
|
flex: 1,
|
|
541
436
|
minHeight: 0,
|
|
542
|
-
}, children: renderTree(fileTree, 0) }))
|
|
437
|
+
}, children: renderTree(fileTree, 0) })) : (_jsx("p", { style: {
|
|
438
|
+
margin: 0,
|
|
439
|
+
padding: '12px 16px',
|
|
440
|
+
color: muted,
|
|
441
|
+
fontFamily: theme.fonts.body,
|
|
442
|
+
fontSize: theme.fontSizes[0],
|
|
443
|
+
}, children: "No files in this model." }))] }), _jsxs("div", { style: {
|
|
543
444
|
position: 'relative',
|
|
544
445
|
flex: 1,
|
|
545
446
|
minWidth: 0,
|
|
546
447
|
minHeight: 0,
|
|
547
448
|
display: 'flex',
|
|
548
449
|
flexDirection: 'column',
|
|
549
|
-
}, children: [_jsx("div", { "data-testid": "construct-
|
|
450
|
+
}, children: [searchActive && (_jsx("div", { style: { flexShrink: 0, padding: '12px 16px 0' }, children: _jsx("input", { type: "search", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search constructs\u2026", "aria-label": "Search constructs", "data-testid": "construct-search", style: {
|
|
451
|
+
width: '100%',
|
|
452
|
+
boxSizing: 'border-box',
|
|
453
|
+
padding: '8px 10px',
|
|
454
|
+
border: `1px solid ${theme.colors.border}`,
|
|
455
|
+
borderRadius: 6,
|
|
456
|
+
background: theme.colors.background,
|
|
457
|
+
color: theme.colors.text,
|
|
458
|
+
fontFamily: theme.fonts.monospace,
|
|
459
|
+
fontSize: theme.fontSizes[1],
|
|
460
|
+
outline: 'none',
|
|
461
|
+
} }) })), _jsx("div", { "data-testid": "construct-signature", style: {
|
|
550
462
|
flex: 1,
|
|
551
463
|
minHeight: 0,
|
|
552
464
|
overflow: 'auto',
|
|
@@ -554,7 +466,7 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
554
466
|
display: 'flex',
|
|
555
467
|
flexDirection: 'column',
|
|
556
468
|
gap: 12,
|
|
557
|
-
}, children:
|
|
469
|
+
}, children: searchResults.length > 0 ? (repoGroups.map((group) => (_jsxs("div", { style: {
|
|
558
470
|
display: 'flex',
|
|
559
471
|
flexDirection: 'column',
|
|
560
472
|
gap: 12,
|
|
@@ -565,35 +477,112 @@ export function ConstructsCatalog({ components, onSelect, title, hideSidebar, de
|
|
|
565
477
|
color: theme.colors.textSecondary ?? muted,
|
|
566
478
|
fontFamily: theme.fonts.body,
|
|
567
479
|
fontSize: theme.fontSizes[1],
|
|
568
|
-
}, children: group.label })] }), group.items.map((
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
//
|
|
572
|
-
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
480
|
+
}, children: group.label })] }), splitByFileRun(group.items).map((run) => {
|
|
481
|
+
const lead = run[0];
|
|
482
|
+
const hasFile = !!lead.file;
|
|
483
|
+
// Every file-backed run gets the combined header, one
|
|
484
|
+
// declaration or many, so the style stays consistent.
|
|
485
|
+
const showHeader = hasFile;
|
|
486
|
+
const runKey = `${group.key}:${lead.file || lead.alias}`;
|
|
487
|
+
const runHasPurpose = run.some((c) => !!c.purpose?.trim());
|
|
488
|
+
const purposeOpen = openPurposes.has(runKey);
|
|
489
|
+
const runFileOpen = drawer != null && hasFile && lead.file === drawer.file;
|
|
490
|
+
return (_jsxs("div", { style: {
|
|
491
|
+
display: 'flex',
|
|
492
|
+
flexDirection: 'column',
|
|
493
|
+
border: `1px solid ${runFileOpen ? accentColor : theme.colors.border}`,
|
|
579
494
|
borderRadius: 8,
|
|
580
495
|
overflow: 'hidden',
|
|
581
496
|
flexShrink: 0,
|
|
582
|
-
}, children:
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
497
|
+
}, children: [showHeader && (_jsxs("div", { style: {
|
|
498
|
+
display: 'flex',
|
|
499
|
+
alignItems: 'center',
|
|
500
|
+
gap: 6,
|
|
501
|
+
width: '100%',
|
|
502
|
+
boxSizing: 'border-box',
|
|
503
|
+
padding: '6px 10px',
|
|
504
|
+
borderBottom: `1px solid ${runFileOpen ? accentColor : theme.colors.border}`,
|
|
505
|
+
background: runFileOpen
|
|
506
|
+
? `${accentColor}14`
|
|
507
|
+
: theme.colors.backgroundSecondary ??
|
|
508
|
+
theme.colors.background,
|
|
509
|
+
color: runFileOpen
|
|
510
|
+
? accentColor
|
|
511
|
+
: theme.colors.textSecondary ?? muted,
|
|
512
|
+
fontFamily: theme.fonts.monospace,
|
|
513
|
+
fontSize: theme.fontSizes[0],
|
|
514
|
+
}, children: [_jsxs("button", { type: "button", title: fileViewer ? `Open ${lead.file}` : lead.file, onClick: fileViewer
|
|
515
|
+
? () => onOpenFile(lead.file)
|
|
516
|
+
: undefined, style: {
|
|
517
|
+
display: 'flex',
|
|
518
|
+
alignItems: 'center',
|
|
519
|
+
gap: 6,
|
|
520
|
+
flex: 1,
|
|
521
|
+
minWidth: 0,
|
|
522
|
+
padding: 0,
|
|
523
|
+
border: 'none',
|
|
524
|
+
background: 'transparent',
|
|
525
|
+
color: 'inherit',
|
|
526
|
+
fontFamily: 'inherit',
|
|
527
|
+
fontSize: 'inherit',
|
|
528
|
+
textAlign: 'left',
|
|
529
|
+
cursor: fileViewer ? 'pointer' : 'default',
|
|
530
|
+
}, children: [_jsx(FileText, { size: 12, style: { flexShrink: 0 } }), _jsx("span", { style: {
|
|
531
|
+
overflow: 'hidden',
|
|
532
|
+
textOverflow: 'ellipsis',
|
|
533
|
+
whiteSpace: 'nowrap',
|
|
534
|
+
}, children: lead.file })] }), runHasPurpose && (_jsx("button", { type: "button", title: purposeOpen ? 'Hide description' : 'Show description', "aria-label": purposeOpen ? 'Hide description' : 'Show description', "aria-expanded": purposeOpen, onClick: () => togglePurpose(runKey), style: {
|
|
535
|
+
display: 'inline-flex',
|
|
536
|
+
alignItems: 'center',
|
|
537
|
+
justifyContent: 'center',
|
|
538
|
+
flexShrink: 0,
|
|
539
|
+
width: 20,
|
|
540
|
+
height: 20,
|
|
541
|
+
padding: 0,
|
|
542
|
+
border: 'none',
|
|
543
|
+
borderRadius: 4,
|
|
544
|
+
background: 'transparent',
|
|
545
|
+
color: purposeOpen
|
|
546
|
+
? accentColor
|
|
547
|
+
: theme.colors.textSecondary ?? muted,
|
|
548
|
+
cursor: 'pointer',
|
|
549
|
+
}, children: _jsx(AlignLeft, { size: 13 }) }))] })), run.map((c, i) => {
|
|
550
|
+
// File-level: every declaration sharing the open file
|
|
551
|
+
// shows the indicator. Line-level: only the declaration
|
|
552
|
+
// actually highlighted gets the inner accent ring.
|
|
553
|
+
const fileOpen = drawer != null && !!c.file && c.file === drawer.file;
|
|
554
|
+
const lineOpen = fileOpen &&
|
|
555
|
+
drawer.startLine != null &&
|
|
556
|
+
c.declarationRef?.startLine === drawer.startLine;
|
|
557
|
+
// A combined header owns the path, so declarations under
|
|
558
|
+
// it drop their own badge; those with no known line just
|
|
559
|
+
// render unnumbered. Without a header, keep the badge.
|
|
560
|
+
const numbered = hasFile && c.declarationRef?.startLine != null;
|
|
561
|
+
return (_jsx("div", { id: `construct-signature-${c.alias}`, style: {
|
|
562
|
+
borderTop: i > 0
|
|
563
|
+
? `1px solid ${theme.colors.border}`
|
|
564
|
+
: undefined,
|
|
565
|
+
boxShadow: lineOpen
|
|
566
|
+
? `inset 0 0 0 1px ${accentColor}`
|
|
567
|
+
: undefined,
|
|
568
|
+
}, children: _jsx(ComponentDeclaration, { component: c, onOpenFile: fileViewer ? onOpenFile : undefined, defaultShowFile: showHeader ? false : !!fileViewer, onRelatedSelect: onRelatedSelect, onInspectSymbol: onInspectSymbol
|
|
569
|
+
? (symbol, ref) => onInspectSymbol({
|
|
570
|
+
purl: c.purl,
|
|
571
|
+
file: c.file,
|
|
572
|
+
symbol,
|
|
573
|
+
ref,
|
|
574
|
+
})
|
|
575
|
+
: undefined, fileOpen: fileOpen, declarationOpen: lineOpen, showRepoIdentity: false, fileBadgeChrome: showHeader ? false : !!fileViewer, hideActions: showHeader, showPurpose: showHeader ? purposeOpen : undefined, lineNumbers: numbered, verification: componentVerification && focusedAlias === c.alias
|
|
576
|
+
? componentVerification
|
|
577
|
+
: undefined }) }, c.alias));
|
|
578
|
+
})] }, runKey));
|
|
592
579
|
})] }, group.key)))) : (_jsx("p", { style: {
|
|
593
580
|
margin: 0,
|
|
594
581
|
color: muted,
|
|
595
582
|
fontFamily: theme.fonts.body,
|
|
596
583
|
fontSize: theme.fontSizes[1],
|
|
597
|
-
}, children:
|
|
584
|
+
}, children: searchQuery.trim()
|
|
585
|
+
? `No constructs match “${searchQuery.trim()}”.`
|
|
586
|
+
: 'No constructs in this model.' })) }), _jsx(FileDrawer, { title: drawer?.file ?? null, onClose: () => setDrawer(null), fillHeight: drawer != null && drawer.startLine == null, children: drawer ? (_jsx(FileDrawerContent, { render: renderDrawerContent, file: drawer.file, startLine: drawer.startLine })) : null })] })] }));
|
|
598
587
|
}
|
|
599
588
|
//# sourceMappingURL=ConstructsCatalog.js.map
|