tickmarkr 1.83.0 → 1.84.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/dist/tui/cockpit/components.d.ts +28 -1
- package/dist/tui/cockpit/components.js +19 -3
- package/dist/tui/cockpit/layout.d.ts +75 -6
- package/dist/tui/cockpit/layout.js +97 -19
- package/dist/tui/cockpit/live.d.ts +14 -1
- package/dist/tui/cockpit/live.js +223 -29
- package/dist/tui/cockpit/pointer.d.ts +261 -0
- package/dist/tui/cockpit/pointer.js +610 -0
- package/dist/tui/cockpit/run-cockpit.d.ts +36 -5
- package/dist/tui/cockpit/run-cockpit.js +145 -27
- package/package.json +1 -1
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Box, useApp, useStdout } from "ink";
|
|
3
|
-
import { cloneElement, useInsertionEffect, useMemo, useRef, } from "react";
|
|
3
|
+
import { cloneElement, createContext, useContext, useEffect, useInsertionEffect, useMemo, useRef, useSyncExternalStore, } from "react";
|
|
4
4
|
import { GLYPHS, PLAIN_COMPACT_LOCKUP, } from "../../brand.js";
|
|
5
5
|
import { allocateBandColumns, BandLines, BodyText, CockpitGrid, composeBandLine, JournalRowPanel, keyRosterLines, Panel, PANEL_CHROME_ROWS, ProgressMeter, Sparkline, StatTile, StatusStrip, } from "./components.js";
|
|
6
6
|
import { fieldReading } from "./derive.js";
|
|
7
7
|
import { initialRunInteractionState, projectRunKeyEntries, reconcileRunInteraction, RUN_SIDE_RAIL_COLUMN_FLOOR, runPanelFocusOrder, runSideRailVisible, selectableRunViewRowIds, } from "./keys.js";
|
|
8
8
|
import { DECISIONS_TAB_TITLE, RUN_VIEWS, runViewById, setupSectionAt, SETUP_SECTIONS, } from "./views.js";
|
|
9
|
-
import { FRAME_HEADER_GAP, FULL_JOURNAL_ROWS, planFrame, SIDEBAR_COLUMN_FLOOR, } from "./layout.js";
|
|
9
|
+
import { FRAME_HEADER_GAP, FRAME_NESTED_ROW_BANDS, FULL_JOURNAL_ROWS, planFrame, RUN_PANEL_HORIZONTAL_CHROME, SIDEBAR_COLUMN_FLOOR, } from "./layout.js";
|
|
10
|
+
import { pointerHoverRow, pointerRestingCell, sessionRailOverride, watchPointerRest, watchSessionRailOverride, } from "./pointer.js";
|
|
10
11
|
import { cellWidth, fitCells, wrapCells } from "./width.js";
|
|
11
12
|
export { deriveRunCockpitData } from "./derive.js";
|
|
12
13
|
export { PANEL_CHROME_ROWS } from "./components.js";
|
|
13
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* The width focus and key routing resolve at, from a measured width — the form
|
|
16
|
+
* used before a plan exists. Once one does, `plannedKeyColumns` (pointer.ts)
|
|
17
|
+
* reads the same answer off the bands the plan actually drew.
|
|
18
|
+
*/
|
|
14
19
|
export function runKeyColumns(columns) {
|
|
15
20
|
return columns >= SIDEBAR_COLUMN_FLOOR
|
|
16
21
|
? Math.max(columns, RUN_SIDE_RAIL_COLUMN_FLOOR)
|
|
@@ -61,8 +66,6 @@ function NarrowHeaderBand({ columns, content, rightStrong = false, }) {
|
|
|
61
66
|
return (_jsxs(CockpitGrid, { columns: columns, columnContents: content, allocation: NARROW_HEADER_ALLOCATION, children: [_jsx(BandLines, { columns: Math.max(1, widths[0]), lines: content[0].lines, emphasis: "strong" }), _jsx(Box, { alignItems: "flex-end", flexDirection: "column", children: _jsx(BandLines, { columns: Math.max(1, widths[1]), lines: content[1].lines, emphasis: rightStrong ? "strong" : "normal" }) })] }));
|
|
62
67
|
}
|
|
63
68
|
const RUN_SIDE_RAIL_WIDTH = 16;
|
|
64
|
-
/** Border plus horizontal padding charged by Panel around its body. */
|
|
65
|
-
const RUN_PANEL_HORIZONTAL_CHROME = 4;
|
|
66
69
|
/** The header caption: the run's identity line, planned as the header's refinable sub-region. */
|
|
67
70
|
function frameCaption(data) {
|
|
68
71
|
return `${data.runId} · ${data.branch} · ${data.status} · ${data.elapsed}`;
|
|
@@ -116,6 +119,36 @@ function setupSectionLines(section, data) {
|
|
|
116
119
|
return ["No parked decisions in this engagement"];
|
|
117
120
|
return parked.map((row) => `${row.taskId} · parked · attempt ${row.attempts ?? 0}`);
|
|
118
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* The plan's item rows, and the ref the frame registers their painted node
|
|
124
|
+
* through. Every body panel draws its list into this one box, so the rectangle
|
|
125
|
+
* the conformance oracle measures off the paint is the rectangle planFrame
|
|
126
|
+
* planned — which is exactly the pin a field standing beside the plan could
|
|
127
|
+
* never carry. Absent on the unsized legacy surface, which has no plan.
|
|
128
|
+
*/
|
|
129
|
+
const ItemRowsBand = createContext(undefined);
|
|
130
|
+
/**
|
|
131
|
+
* The identity of the row a live pointer is resting on, or none. It is drawn
|
|
132
|
+
* state and only drawn state: nothing reads it back, no interaction state
|
|
133
|
+
* carries it, and the one provider is fed by a `hover` prop the frame's own
|
|
134
|
+
* callers supply. A capture supplies none — `capture.ts` builds this frame from
|
|
135
|
+
* data, size and interaction alone — so every golden, anchor and colour capture
|
|
136
|
+
* records the pointerless appearance by construction rather than by a guard
|
|
137
|
+
* stripping a highlight back out afterwards.
|
|
138
|
+
*/
|
|
139
|
+
const HoveredRow = createContext(null);
|
|
140
|
+
/**
|
|
141
|
+
* A body panel's list, drawn into the plan's own item rows and nowhere else.
|
|
142
|
+
* The band is consumed by the outermost panel that asks for it — a panel nested
|
|
143
|
+
* inside that one (the RUN view's own journal) is part of the list, not a second
|
|
144
|
+
* claim on the region, so the band is cleared for everything below.
|
|
145
|
+
*/
|
|
146
|
+
function ItemRows({ children }) {
|
|
147
|
+
const band = useContext(ItemRowsBand);
|
|
148
|
+
if (band === undefined)
|
|
149
|
+
return _jsx(_Fragment, { children: children });
|
|
150
|
+
return (_jsx(Box, { ref: band.regionRef, flexDirection: "column", height: band.region.rows, width: band.region.columns, flexShrink: 0, children: _jsx(ItemRowsBand.Provider, { value: undefined, children: children }) }));
|
|
151
|
+
}
|
|
119
152
|
/**
|
|
120
153
|
* The setup tab's body, drawn inside the same row budget the watch tab's views
|
|
121
154
|
* are drawn inside — the tab an operator switches to can never be taller than
|
|
@@ -207,17 +240,44 @@ function windowedRows(rows, bodyRows, selection) {
|
|
|
207
240
|
...(selectedIndex < 0 ? {} : { selection: selectedIndex - offset }),
|
|
208
241
|
};
|
|
209
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* The identities the body's rows carry in the order the body draws them — the
|
|
245
|
+
* same window `SizedJournalPanel` paints, so a hit on a body row resolves to
|
|
246
|
+
* the row an operator is looking at rather than to a position in a collection
|
|
247
|
+
* the frame scrolled past. A body that draws no selectable list (the overview,
|
|
248
|
+
* the journal tail, an open detail, the help overlay, the decisions tab) owns
|
|
249
|
+
* no rows here.
|
|
250
|
+
*/
|
|
251
|
+
export function drawnRunViewRowIds(data, interaction, bodyRows) {
|
|
252
|
+
if (interaction.tab === "setup"
|
|
253
|
+
|| interaction.activeView === "run"
|
|
254
|
+
|| interaction.opened !== null
|
|
255
|
+
|| interaction.help)
|
|
256
|
+
return [];
|
|
257
|
+
const rows = deriveRunViewRows(data, interaction.activeView, interaction.filterQuery);
|
|
258
|
+
return selectableRunViewRowIds(interaction.activeView, windowedRows(rows, bodyRows, interaction.selection).rows.map((row) => row.id));
|
|
259
|
+
}
|
|
210
260
|
function SizedJournalPanel({ rows, bodyRows, title, selection, focused, }) {
|
|
211
261
|
const window = windowedRows(rows, bodyRows, selection);
|
|
262
|
+
// The hovered row is carried as its identity, never as a screen row: it was
|
|
263
|
+
// resolved through the plan once, and this panel finds it in the very window
|
|
264
|
+
// it is about to paint or does not draw it at all. A list this pointer cell
|
|
265
|
+
// acts on nothing in — the passive overview tail, the journal's unselectable
|
|
266
|
+
// rows — finds nothing here, so no row is decorated for being passed over.
|
|
267
|
+
const hovered = useContext(HoveredRow);
|
|
268
|
+
const hover = hovered === null
|
|
269
|
+
? -1
|
|
270
|
+
: window.rows.findIndex((row) => row.id === hovered);
|
|
212
271
|
const emptyRows = Math.max(0, bodyRows - window.rows.length);
|
|
213
272
|
const journal = JournalRowPanel({
|
|
214
273
|
rows: window.rows,
|
|
215
274
|
title,
|
|
216
275
|
selection: window.selection,
|
|
217
276
|
focused,
|
|
277
|
+
...(hover < 0 ? {} : { hover }),
|
|
218
278
|
});
|
|
219
279
|
const journalProps = journal.props;
|
|
220
|
-
return cloneElement(journal, {}, journalProps.children, emptyRows > 0 ? _jsx(Box, { height: emptyRows }, "journal-space") : null);
|
|
280
|
+
return cloneElement(journal, {}, _jsxs(ItemRows, { children: [journalProps.children, emptyRows > 0 ? _jsx(Box, { height: emptyRows }, "journal-space") : null] }, "items"));
|
|
221
281
|
}
|
|
222
282
|
/** A body panel (help overlay, opened event) padded to the journal's rows. */
|
|
223
283
|
function SizedLinesPanel({ title, lines, bodyRows, wrapColumns, focused, flexGrow, }) {
|
|
@@ -226,7 +286,7 @@ function SizedLinesPanel({ title, lines, bodyRows, wrapColumns, focused, flexGro
|
|
|
226
286
|
: lines.flatMap((line) => composeBandLine(line, wrapColumns));
|
|
227
287
|
const visible = drawnLines.slice(0, bodyRows);
|
|
228
288
|
const emptyRows = Math.max(0, bodyRows - visible.length);
|
|
229
|
-
return (
|
|
289
|
+
return (_jsx(Panel, { title: title, focused: focused, flexGrow: flexGrow, children: _jsxs(ItemRows, { children: [visible.map((line, index) => (_jsx(Box, { height: 1, overflow: "hidden", children: _jsx(BodyText, { children: line }) }, `${index}:${line}`))), emptyRows > 0 && _jsx(Box, { height: emptyRows })] }) }));
|
|
230
290
|
}
|
|
231
291
|
/** The lines the help overlay draws: every advertised key and its promise. */
|
|
232
292
|
function helpLines(entries) {
|
|
@@ -246,7 +306,7 @@ function RunViewPanel({ data, viewId, interaction, focused, keyEntries, bodyRows
|
|
|
246
306
|
const view = runViewById(viewId);
|
|
247
307
|
const rows = deriveRunViewRows(data, viewId, interaction.filterQuery);
|
|
248
308
|
if (interaction.help) {
|
|
249
|
-
return (_jsx(Panel, { title: "HELP", focused: focused, flexGrow: 1, children: helpLines(keyEntries).map((line) => _jsx(BodyText, { children: line }, line)) }));
|
|
309
|
+
return (_jsx(Panel, { title: "HELP", focused: focused, flexGrow: 1, children: _jsx(ItemRows, { children: helpLines(keyEntries).map((line) => _jsx(BodyText, { children: line }, line)) }) }));
|
|
250
310
|
}
|
|
251
311
|
const opened = interaction.opened === null
|
|
252
312
|
? undefined
|
|
@@ -255,7 +315,7 @@ function RunViewPanel({ data, viewId, interaction, focused, keyEntries, bodyRows
|
|
|
255
315
|
return (_jsx(SizedLinesPanel, { title: view.detailTitle, lines: eventLines(opened), bodyRows: bodyRows, wrapColumns: bodyColumns, focused: focused, flexGrow: 1 }));
|
|
256
316
|
}
|
|
257
317
|
if (rows.length === 0) {
|
|
258
|
-
return (_jsx(Panel, { title: view.title, focused: focused, flexGrow: 1, children: _jsxs(BodyText, { emphasis: "dim", children: ["No ", view.id, " in this engagement"] }) }));
|
|
318
|
+
return (_jsx(Panel, { title: view.title, focused: focused, flexGrow: 1, children: _jsx(ItemRows, { children: _jsxs(BodyText, { emphasis: "dim", children: ["No ", view.id, " in this engagement"] }) }) }));
|
|
259
319
|
}
|
|
260
320
|
return (_jsx(SizedJournalPanel, { rows: rows, bodyRows: bodyRows, title: interaction.filterPrompt
|
|
261
321
|
? `${view.title} /${interaction.filterQuery}`
|
|
@@ -414,12 +474,13 @@ function resolveRunViewContent(bodySpan, bodyColumns, data) {
|
|
|
414
474
|
}
|
|
415
475
|
/** What fills the regions this paint draws, resolved from the plan's own spans. */
|
|
416
476
|
function resolveRegionContent(plan, data, interaction) {
|
|
417
|
-
const
|
|
418
|
-
const bodySpan = bodyRegion.rows;
|
|
477
|
+
const bodySpan = requiredRegion(plan, "body").rows;
|
|
419
478
|
const sideRails = plan.band === "sidebar";
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const
|
|
479
|
+
// The plan's own item rows, consumed unmodified: the paint draws its list at
|
|
480
|
+
// this region and the pointer resolves a hit through it — one geometry.
|
|
481
|
+
const items = requiredRegion(plan, "items");
|
|
482
|
+
const bodyColumns = items.columns;
|
|
483
|
+
const common = { items, sideRails };
|
|
423
484
|
if (interaction.tab !== "setup" && interaction.activeView === "run") {
|
|
424
485
|
return {
|
|
425
486
|
...common,
|
|
@@ -447,11 +508,11 @@ function resolveRegionContent(plan, data, interaction) {
|
|
|
447
508
|
/** The RUN view drawn at its planned spans: every row budget arrives resolved. */
|
|
448
509
|
function LadderRunPanel({ data, content, compactMeterWidth, interaction, keyEntries, focused = true, }) {
|
|
449
510
|
if (interaction?.help === true) {
|
|
450
|
-
return (_jsx(SizedLinesPanel, { title: "HELP", lines: helpLines(keyEntries), bodyRows: content.
|
|
511
|
+
return (_jsx(SizedLinesPanel, { title: "HELP", lines: helpLines(keyEntries), bodyRows: content.items.rows, wrapColumns: content.items.columns, focused: focused, flexGrow: 1 }));
|
|
451
512
|
}
|
|
452
|
-
return (
|
|
453
|
-
|
|
454
|
-
|
|
513
|
+
return (_jsx(Panel, { title: "RUN", focused: focused, flexGrow: 1, children: _jsxs(ItemRows, { children: [_jsx(RunStats, { data: data, mode: content.stats }), content.progressBar && (content.approvedProgress
|
|
514
|
+
? _jsx(ApprovedProgress, { data: data, caption: content.progressCaption })
|
|
515
|
+
: (_jsx(CompactProgress, { data: data, caption: content.progressCaption, meterWidth: compactMeterWidth }))), content.journal > 0 && (_jsx(JournalBody, { data: data, interaction: interaction, bodyRows: content.journal, keyEntries: keyEntries }))] }) }));
|
|
455
516
|
}
|
|
456
517
|
function LegacyRunPanel({ data, interaction, keyEntries, focused = true, }) {
|
|
457
518
|
const rows = deriveRunViewRows(data, "journal");
|
|
@@ -524,14 +585,14 @@ function RunCockpitPaint({ data, columns, plan, content, interaction, keyProject
|
|
|
524
585
|
const setup = currentInteraction.tab === "setup";
|
|
525
586
|
return (_jsxs(Box, { flexDirection: "column", width: plan.size.columns, height: plan.size.rows, overflow: "hidden", children: [_jsxs(FrameBand, { rows: header.rows, row: true, regionRef: regionRef("header"), children: [_jsx(Box, { flexGrow: 1, flexShrink: 1, height: header.rows, overflow: "hidden", children: _jsx(PlannedHeader, { data: data, plan: plan, columns: plan.size.columns - (caption?.columns ?? 0) }) }), caption !== undefined && (_jsx(Box, { ref: regionRef("caption"), width: caption.columns, height: caption.rows, flexShrink: 0, overflow: "hidden", children: _jsx(PlannedCaption, { data: data, columns: caption.columns }) }))] }), _jsx(FrameBand, { rows: requiredRegion(plan, "rule").rows, regionRef: regionRef("rule"), children: _jsx(RuleBand, { columns: plan.size.columns }) }), strip !== undefined && (_jsx(FrameBand, { rows: strip.rows, regionRef: regionRef("strip"), children: _jsx(ViewStrip, { selection: railSelection, columns: strip.columns }) })), _jsxs(FrameBand, { rows: body.rows, row: true, children: [rail !== undefined && (_jsx(Box, { ref: regionRef("rail"), width: rail.columns, height: rail.rows, flexShrink: 0, overflow: "hidden", children: setup
|
|
526
587
|
? (_jsx(SectionsBand, { focused: railFocused, selection: railSelection, columns: rail.columns }))
|
|
527
|
-
: (_jsx(RailBand, { data: data, focused: railFocused, selection: railSelection, columns: rail.columns, rows: rail.rows, sidebar: plan.sidebar })) })), _jsx(Box, { ref: regionRef("body"), width: body.columns, height: body.rows, flexShrink: 0, flexDirection: "column", overflow: "hidden", children: setup
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
588
|
+
: (_jsx(RailBand, { data: data, focused: railFocused, selection: railSelection, columns: rail.columns, rows: rail.rows, sidebar: plan.sidebar })) })), _jsx(Box, { ref: regionRef("body"), width: body.columns, height: body.rows, flexShrink: 0, flexDirection: "column", overflow: "hidden", children: _jsx(ItemRowsBand.Provider, { value: { region: content.items, regionRef: regionRef("items") }, children: setup
|
|
589
|
+
? (_jsx(SetupTabPanel, { data: data, selection: railSelection, focused: contentFocused, keyEntries: keyEntries, help: currentInteraction.help, bodyRows: content.items.rows, bodyColumns: content.items.columns }))
|
|
590
|
+
: activeView === "run"
|
|
591
|
+
? (_jsx(LadderRunPanel, { data: data, content: content, compactMeterWidth: content.sideRails ? undefined : 24, interaction: currentInteraction, keyEntries: keyEntries, focused: contentFocused }))
|
|
592
|
+
: (_jsx(RunViewPanel, { data: data, viewId: activeView, interaction: currentInteraction, focused: contentFocused, keyEntries: keyEntries, bodyRows: content.items.rows, bodyColumns: content.items.columns })) }) })] }), _jsx(FrameBand, { rows: requiredRegion(plan, "rule2").rows, regionRef: regionRef("rule2"), children: _jsx(RuleBand, { columns: plan.size.columns }) }), tail !== undefined && (_jsx(FrameBand, { rows: tail.rows, regionRef: regionRef("tail"), children: _jsx(TailBand, { data: data, rows: tail.rows, columns: tail.columns, pendingWrites: plan.content.tail === "pending-writes" }) })), _jsx(FrameBand, { rows: requiredRegion(plan, "status").rows, regionRef: regionRef("status"), children: _jsx(StatusStrip, { items: reportedStatusItems, width: plan.size.columns }) }), _jsx(FrameBand, { rows: requiredRegion(plan, "keybar").rows, regionRef: regionRef("keybar"), children: _jsx(RunKeybar, { entries: keyEntries, width: plan.size.columns, help: currentInteraction.help }) })] }));
|
|
532
593
|
}
|
|
533
594
|
/** Resolve every draw-time input; the plan is planFrame's own return, consumed unmodified. */
|
|
534
|
-
export function planRunCockpitFrame({ data, columns, rows, interaction, keyProjection = projectRunKeyEntries, }) {
|
|
595
|
+
export function planRunCockpitFrame({ data, columns, rows, interaction, keyProjection = projectRunKeyEntries, railColumns, }) {
|
|
535
596
|
const suppliedInteraction = interaction ?? initialRunInteractionState();
|
|
536
597
|
const suppliedRows = deriveRunViewRows(data, suppliedInteraction.activeView, suppliedInteraction.filterQuery);
|
|
537
598
|
const rowIds = suppliedRows.map((row) => row.id);
|
|
@@ -547,6 +608,7 @@ export function planRunCockpitFrame({ data, columns, rows, interaction, keyProje
|
|
|
547
608
|
tab: repaired.tab,
|
|
548
609
|
detail: repaired.opened !== null,
|
|
549
610
|
captionCells: cellWidth(frameCaption(data)),
|
|
611
|
+
...(railColumns === undefined ? {} : { railColumns }),
|
|
550
612
|
};
|
|
551
613
|
const plan = planFrame(size, view, state);
|
|
552
614
|
const content = rows === undefined || plan.kind !== "frame"
|
|
@@ -565,15 +627,56 @@ export function planRunCockpitFrame({ data, columns, rows, interaction, keyProje
|
|
|
565
627
|
};
|
|
566
628
|
}
|
|
567
629
|
/** The public surface always enters paint through one measured plan. */
|
|
568
|
-
export function RunCockpitFrame({ data, columns, rows, interaction, keyProjection = projectRunKeyEntries, }) {
|
|
630
|
+
export function RunCockpitFrame({ data, columns, rows, interaction, keyProjection = projectRunKeyEntries, onCommittedFrame, }) {
|
|
631
|
+
// The session's panel resize override, read from the pointer layer the live
|
|
632
|
+
// drag feeds and redrawn when the boundary moves. It enters the frame only
|
|
633
|
+
// as an input to this planning call — the drawn frame is the recomputed
|
|
634
|
+
// plan, so a resize can never paint beside the plan. Nothing persists it: a
|
|
635
|
+
// relaunch plans the default layout.
|
|
636
|
+
const railOverride = useSyncExternalStore(watchSessionRailOverride, sessionRailOverride, sessionRailOverride);
|
|
569
637
|
const plannedFrame = useMemo(() => planRunCockpitFrame({
|
|
570
638
|
data,
|
|
571
639
|
columns,
|
|
572
640
|
rows,
|
|
573
641
|
interaction,
|
|
574
642
|
keyProjection,
|
|
575
|
-
|
|
576
|
-
|
|
643
|
+
railColumns: railOverride ?? undefined,
|
|
644
|
+
}), [data, columns, rows, interaction, keyProjection, railOverride]);
|
|
645
|
+
useEffect(() => {
|
|
646
|
+
onCommittedFrame?.(plannedFrame, data);
|
|
647
|
+
}, [plannedFrame, data, onCommittedFrame]);
|
|
648
|
+
// Where the pointer is resting, read from the pointer layer that the live
|
|
649
|
+
// input path feeds and redrawn when it moves. There is no prop beside it: a
|
|
650
|
+
// hover cannot be handed to this frame at all, only reported to the terminal.
|
|
651
|
+
const resting = useSyncExternalStore(watchPointerRest, pointerRestingCell, pointerRestingCell);
|
|
652
|
+
// And only a frame that publishes its geometry for hit testing can be hovered
|
|
653
|
+
// — publishing it is what makes a pointer report resolve against this frame
|
|
654
|
+
// at all. A capture publishes none and resolves none, so no golden, anchor or
|
|
655
|
+
// colour capture can carry a highlight however the pointer came to rest: the
|
|
656
|
+
// pointerless appearance is what that path draws by construction, not what a
|
|
657
|
+
// guard strips back out of it.
|
|
658
|
+
const hover = onCommittedFrame === undefined ? null : resting;
|
|
659
|
+
const hoveredRow = useMemo(() => {
|
|
660
|
+
const plan = plannedFrame.plan;
|
|
661
|
+
if (hover === null || plan.kind !== "frame"
|
|
662
|
+
|| plannedFrame.content === undefined)
|
|
663
|
+
return null;
|
|
664
|
+
const interaction = plannedFrame.interaction;
|
|
665
|
+
// The whole surface a click resolves through, not only the geometry: what a
|
|
666
|
+
// click at this cell ACTS ON is the highlight's answer, and that route
|
|
667
|
+
// passes through the focus the frame is drawn with and the rows a key may
|
|
668
|
+
// stand on. Deriving the highlight from the row lookup alone would advertise
|
|
669
|
+
// a row of the drawn view while the rail marker stands on another — where
|
|
670
|
+
// the click opens the marked view and selects nothing.
|
|
671
|
+
return pointerHoverRow({
|
|
672
|
+
plan,
|
|
673
|
+
interaction,
|
|
674
|
+
drawnRowIds: drawnRunViewRowIds(data, interaction, plannedFrame.content.items.rows),
|
|
675
|
+
rowIds: selectableRunViewRowIds(interaction.activeView, deriveRunViewRows(data, interaction.activeView, interaction.filterQuery)
|
|
676
|
+
.map((row) => row.id)),
|
|
677
|
+
}, hover) ?? null;
|
|
678
|
+
}, [plannedFrame, data, hover]);
|
|
679
|
+
return (_jsx(HoveredRow.Provider, { value: hoveredRow, children: _jsx(RunCockpitFrameFromPlan, { data: data, plannedFrame: plannedFrame }) }));
|
|
577
680
|
}
|
|
578
681
|
function requiredRegion(plan, id) {
|
|
579
682
|
const region = plan.regions.find((candidate) => candidate.id === id);
|
|
@@ -588,6 +691,21 @@ function assertPlanSpans(plan) {
|
|
|
588
691
|
let nextRow = 0;
|
|
589
692
|
for (const [id, span] of Object.entries(plan.rowSpans)) {
|
|
590
693
|
const region = requiredRegion(plan, id);
|
|
694
|
+
const host = FRAME_NESTED_ROW_BANDS[id];
|
|
695
|
+
if (host !== undefined) {
|
|
696
|
+
// A nested band refines its host's rows rather than tiling beside them,
|
|
697
|
+
// so it advances no row cursor — and is held to its span and to staying
|
|
698
|
+
// inside the band that hosts it.
|
|
699
|
+
const within = requiredRegion(plan, host);
|
|
700
|
+
if (region.rows !== span ||
|
|
701
|
+
region.row < within.row ||
|
|
702
|
+
region.row + region.rows > within.row + within.rows ||
|
|
703
|
+
region.column < within.column ||
|
|
704
|
+
region.column + region.columns > within.column + within.columns) {
|
|
705
|
+
throw new Error(`planned row region ${id} has offset/span ${region.row}/${region.rows} column ${region.column}/${region.columns}, expected span ${span} inside ${host} at ${within.row}/${within.rows} column ${within.column}/${within.columns}`);
|
|
706
|
+
}
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
591
709
|
const fullWidth = id !== "body";
|
|
592
710
|
if (region.row !== nextRow ||
|
|
593
711
|
region.rows !== span ||
|