testomatio-editor-blocks 0.4.73 → 0.4.75
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/package/editor/blocks/step.js +106 -73
- package/package/editor/blocks/stepField.d.ts +11 -0
- package/package/editor/blocks/stepField.js +120 -4
- package/package/editor/customMarkdownConverter.js +54 -34
- package/package/styles.css +76 -0
- package/package.json +1 -1
- package/src/editor/blocks/step.tsx +137 -106
- package/src/editor/blocks/stepField.tsx +163 -5
- package/src/editor/customMarkdownConverter.test.ts +59 -0
- package/src/editor/customMarkdownConverter.ts +53 -36
- package/src/editor/styles.css +76 -0
- package/package/editor/blocks/useDeferredMount.d.ts +0 -26
- package/package/editor/blocks/useDeferredMount.js +0 -54
- package/src/editor/blocks/useDeferredMount.ts +0 -66
package/package/styles.css
CHANGED
|
@@ -631,6 +631,30 @@ html.dark .bn-step-editor .overtype-wrapper .overtype-preview a.step-preview-lin
|
|
|
631
631
|
vertical-align: 1px;
|
|
632
632
|
}
|
|
633
633
|
|
|
634
|
+
/* Read-only preview parity for compact reading rows: the live compact field
|
|
635
|
+
tightens padding on the OverType layers (above), which the static preview
|
|
636
|
+
doesn't have, so apply the same padding to the flow `--preview` box. */
|
|
637
|
+
.bn-teststep--compact .bn-step-editor--preview {
|
|
638
|
+
padding: 4px 12px;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/* Same "Expected" reading badge for the static preview. The static preview is a
|
|
642
|
+
single flow box (not OverType's per-line divs), so the badge goes on its own
|
|
643
|
+
::before. */
|
|
644
|
+
.bn-teststep--collapsed .bn-step-editor--preview[data-step-field="expected"]::before {
|
|
645
|
+
content: "Expected";
|
|
646
|
+
display: inline-block;
|
|
647
|
+
margin-right: 8px;
|
|
648
|
+
padding: 0 6px;
|
|
649
|
+
border-radius: 4px;
|
|
650
|
+
background: var(--step-bg-light);
|
|
651
|
+
color: var(--step-muted);
|
|
652
|
+
font-size: 11px;
|
|
653
|
+
font-weight: 600;
|
|
654
|
+
line-height: 18px;
|
|
655
|
+
vertical-align: 1px;
|
|
656
|
+
}
|
|
657
|
+
|
|
634
658
|
.bn-teststep__view-toggle--compact svg {
|
|
635
659
|
color: var(--step-muted);
|
|
636
660
|
}
|
|
@@ -1432,6 +1456,58 @@ html.dark .bn-step-editor--preview {
|
|
|
1432
1456
|
color: #e5e5e5;
|
|
1433
1457
|
}
|
|
1434
1458
|
|
|
1459
|
+
/* Wrapper around a non-edited step's read-only preview. Focusable so Tab enters
|
|
1460
|
+
a step (which mounts its editor); no lingering outline since focus moves to
|
|
1461
|
+
the freshly-mounted field immediately. */
|
|
1462
|
+
.bn-teststep-preview-wrapper {
|
|
1463
|
+
outline: none;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
/* Inline decorations inside the read-only preview mirror the live OverType
|
|
1467
|
+
preview, which uses the same step-preview-* classes. The live rules are
|
|
1468
|
+
scoped to `.overtype-wrapper .overtype-preview` (which the static preview
|
|
1469
|
+
doesn't render), so the same declarations are repeated here for the flow
|
|
1470
|
+
`--preview` container. */
|
|
1471
|
+
.bn-step-editor--preview a.step-preview-link {
|
|
1472
|
+
color: #4f46e5;
|
|
1473
|
+
text-decoration: underline;
|
|
1474
|
+
pointer-events: none;
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
.bn-step-editor--preview strong.step-preview-bold {
|
|
1478
|
+
-webkit-text-stroke: 0.5px currentColor;
|
|
1479
|
+
font-weight: inherit;
|
|
1480
|
+
color: inherit;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
.bn-step-editor--preview em.step-preview-italic {
|
|
1484
|
+
font-style: italic;
|
|
1485
|
+
color: inherit;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
.bn-step-editor--preview code.step-preview-code {
|
|
1489
|
+
background-color: transparent;
|
|
1490
|
+
font-family: inherit;
|
|
1491
|
+
font-size: inherit;
|
|
1492
|
+
color: rgb(146, 64, 14);
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
.bn-step-editor--preview img {
|
|
1496
|
+
display: block;
|
|
1497
|
+
max-width: 100%;
|
|
1498
|
+
border-radius: 0.65rem;
|
|
1499
|
+
margin: 0.5rem 0;
|
|
1500
|
+
pointer-events: none;
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
html.dark .bn-step-editor--preview code.step-preview-code {
|
|
1504
|
+
color: rgba(251, 191, 36, 1);
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
html.dark .bn-step-editor--preview a.step-preview-link {
|
|
1508
|
+
color: rgba(129, 140, 248, 1);
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1435
1511
|
.bn-step-editor.bn-step-editor--focused {
|
|
1436
1512
|
outline: none;
|
|
1437
1513
|
box-shadow: none;
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createReactBlockSpec, useEditorChange } from "@blocknote/react";
|
|
2
|
-
import { useCallback, useEffect, useMemo, useRef, useState, type
|
|
3
|
-
import { StepField } from "./stepField";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState, type MouseEvent as ReactMouseEvent } from "react";
|
|
3
|
+
import { StepField, StepFieldPreview } from "./stepField";
|
|
4
4
|
import { StepHorizontalView } from "./stepHorizontalView";
|
|
5
|
-
import { useDeferredMount } from "./useDeferredMount";
|
|
6
5
|
import { useStepImageUpload } from "../stepImageUpload";
|
|
7
6
|
import type { StepSuggestion } from "../stepAutocomplete";
|
|
8
7
|
|
|
@@ -50,6 +49,36 @@ const writeStepViewMode = (mode: StepViewMode) => {
|
|
|
50
49
|
}
|
|
51
50
|
};
|
|
52
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Subscribes to the globally-shared step view mode. The mode lives in
|
|
54
|
+
* localStorage and changes are broadcast via the `bn-step-view-mode` event
|
|
55
|
+
* (same tab) and the `storage` event (other tabs), so toggling it on any step
|
|
56
|
+
* re-renders every step — including the read-only previews — into the new mode.
|
|
57
|
+
*/
|
|
58
|
+
function useStepViewMode(): StepViewMode {
|
|
59
|
+
const [viewMode, setViewMode] = useState<StepViewMode>(() => readStepViewMode());
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (typeof window === "undefined") {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const handleStorage = (event: StorageEvent) => {
|
|
65
|
+
if (event.key === VIEW_MODE_KEY) {
|
|
66
|
+
setViewMode(readStepViewMode());
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const handleLocal = () => {
|
|
70
|
+
setViewMode(readStepViewMode());
|
|
71
|
+
};
|
|
72
|
+
window.addEventListener("storage", handleStorage);
|
|
73
|
+
window.addEventListener("bn-step-view-mode", handleLocal as EventListener);
|
|
74
|
+
return () => {
|
|
75
|
+
window.removeEventListener("storage", handleStorage);
|
|
76
|
+
window.removeEventListener("bn-step-view-mode", handleLocal as EventListener);
|
|
77
|
+
};
|
|
78
|
+
}, []);
|
|
79
|
+
return viewMode;
|
|
80
|
+
}
|
|
81
|
+
|
|
53
82
|
/**
|
|
54
83
|
* Returns true when a normalised (lowercased, trailing-punctuation-stripped)
|
|
55
84
|
* heading text looks like a "Steps" heading.
|
|
@@ -251,76 +280,64 @@ export function computeStepNumber(allBlocks: any[], blockId: string): number {
|
|
|
251
280
|
return count;
|
|
252
281
|
}
|
|
253
282
|
|
|
254
|
-
/** Strip the most common inline markdown markers for a readable static preview. */
|
|
255
|
-
function stripMarkdownForPreview(text: string): string {
|
|
256
|
-
return text
|
|
257
|
-
.replace(/!\[[^\]]*\]\([^)]*\)/g, "")
|
|
258
|
-
.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
259
|
-
.replace(/(\*\*|__|\*|_|~~|`)/g, "")
|
|
260
|
-
.replace(/<\/?[^>]+>/g, "")
|
|
261
|
-
.trim();
|
|
262
|
-
}
|
|
263
|
-
|
|
264
283
|
/**
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
284
|
+
* Read-only stand-in rendered for every step that isn't currently being edited.
|
|
285
|
+
* It mirrors the live step's structure for the active view mode and renders each
|
|
286
|
+
* field via {@link StepFieldPreview} — a faithful, formatted reading view with
|
|
287
|
+
* no OverType editor. Only the focused step ever mounts an editor, so scrolling
|
|
288
|
+
* never mounts/tears down editors and the list stays flicker-free.
|
|
289
|
+
*
|
|
290
|
+
* Field visibility is gated on the raw trimmed props (matching the live step's
|
|
291
|
+
* `isDataVisible`/`isExpectedVisible`) so the same fields appear in both states.
|
|
268
292
|
*/
|
|
269
293
|
function TestStepPreview({
|
|
270
294
|
blockId,
|
|
271
295
|
stepNumber,
|
|
296
|
+
viewMode,
|
|
272
297
|
stepTitle,
|
|
273
298
|
stepData,
|
|
274
299
|
expectedResult,
|
|
275
300
|
}: {
|
|
276
301
|
blockId: string;
|
|
277
302
|
stepNumber: number;
|
|
303
|
+
viewMode: StepViewMode;
|
|
278
304
|
stepTitle: string;
|
|
279
305
|
stepData: string;
|
|
280
306
|
expectedResult: string;
|
|
281
307
|
}) {
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
const
|
|
308
|
+
const compactMode = viewMode === "compact";
|
|
309
|
+
const hasData = stepData.trim().length > 0;
|
|
310
|
+
const hasExpected = expectedResult.trim().length > 0;
|
|
285
311
|
|
|
286
312
|
return (
|
|
287
|
-
<div
|
|
313
|
+
<div
|
|
314
|
+
className={`bn-teststep${compactMode ? " bn-teststep--compact bn-teststep--collapsed" : ""}`}
|
|
315
|
+
data-block-id={blockId}
|
|
316
|
+
>
|
|
288
317
|
<div className="bn-teststep__timeline">
|
|
289
318
|
<span className="bn-teststep__number">{stepNumber}</span>
|
|
290
319
|
<div className="bn-teststep__line" />
|
|
291
320
|
</div>
|
|
292
321
|
<div className="bn-teststep__content">
|
|
293
|
-
|
|
294
|
-
<
|
|
295
|
-
|
|
296
|
-
<div className="bn-step-field">
|
|
297
|
-
<div className="bn-step-editor bn-step-editor--multiline bn-step-editor--preview">
|
|
298
|
-
{titleText || " "}
|
|
299
|
-
</div>
|
|
300
|
-
</div>
|
|
301
|
-
{dataText ? (
|
|
302
|
-
<div className="bn-step-field">
|
|
303
|
-
<div className="bn-step-editor bn-step-editor--multiline bn-step-editor--preview">
|
|
304
|
-
{dataText}
|
|
305
|
-
</div>
|
|
306
|
-
</div>
|
|
307
|
-
) : null}
|
|
308
|
-
{expectedText ? (
|
|
309
|
-
<div className="bn-step-field">
|
|
310
|
-
<div className="bn-step-editor bn-step-editor--multiline bn-step-editor--preview">
|
|
311
|
-
{expectedText}
|
|
312
|
-
</div>
|
|
322
|
+
{!compactMode && (
|
|
323
|
+
<div className="bn-teststep__header">
|
|
324
|
+
<span className="bn-teststep__title">Step</span>
|
|
313
325
|
</div>
|
|
314
|
-
)
|
|
326
|
+
)}
|
|
327
|
+
<StepFieldPreview value={stepTitle} fieldName="title" />
|
|
328
|
+
{hasData ? <StepFieldPreview value={stepData} fieldName="data" /> : null}
|
|
329
|
+
{hasExpected ? <StepFieldPreview value={expectedResult} fieldName="expected" /> : null}
|
|
315
330
|
</div>
|
|
316
331
|
</div>
|
|
317
332
|
);
|
|
318
333
|
}
|
|
319
334
|
|
|
320
335
|
/**
|
|
321
|
-
* Wrapper that
|
|
322
|
-
*
|
|
323
|
-
*
|
|
336
|
+
* Wrapper that mounts the (expensive) interactive step editor only while the
|
|
337
|
+
* step is being edited. Every other step renders the read-only
|
|
338
|
+
* {@link TestStepPreview}, so a document of any size keeps at most one OverType
|
|
339
|
+
* editor alive — scrolling never mounts or tears down editors, which is what
|
|
340
|
+
* keeps the list flicker-free (and pasting/loading a large document fast).
|
|
324
341
|
*
|
|
325
342
|
* The step number is tracked here and pushed down as a prop. We subscribe to
|
|
326
343
|
* editor changes but bail out of re-rendering when the number is unchanged, so
|
|
@@ -329,14 +346,17 @@ function TestStepPreview({
|
|
|
329
346
|
function TestStepBlock({ block, editor }: { block: any; editor: any }) {
|
|
330
347
|
// An empty step is almost always a freshly-inserted one that needs to focus
|
|
331
348
|
// its title immediately, so mount its real editor eagerly. Steps with content
|
|
332
|
-
//
|
|
349
|
+
// start as a cheap read-only preview and upgrade on click/focus.
|
|
333
350
|
const isEmptyStep =
|
|
334
351
|
!((block.props.stepTitle as string) || "") &&
|
|
335
352
|
!((block.props.stepData as string) || "") &&
|
|
336
353
|
!((block.props.expectedResult as string) || "");
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
354
|
+
const viewMode = useStepViewMode();
|
|
355
|
+
const [editing, setEditing] = useState(isEmptyStep);
|
|
356
|
+
// Set when editing begins from a click/focus on the preview, so the freshly
|
|
357
|
+
// mounted editor takes focus (a single click starts editing). Cleared after
|
|
358
|
+
// the editor consumes it.
|
|
359
|
+
const focusOnMountRef = useRef(false);
|
|
340
360
|
const [stepNumber, setStepNumber] = useState(() =>
|
|
341
361
|
computeStepNumber(editor.document, block.id),
|
|
342
362
|
);
|
|
@@ -350,31 +370,54 @@ function TestStepBlock({ block, editor }: { block: any; editor: any }) {
|
|
|
350
370
|
setStepNumber((prev) => (prev === next ? prev : next));
|
|
351
371
|
}, editor);
|
|
352
372
|
|
|
353
|
-
|
|
373
|
+
const beginEditing = useCallback(() => {
|
|
374
|
+
focusOnMountRef.current = true;
|
|
375
|
+
setEditing(true);
|
|
376
|
+
}, []);
|
|
377
|
+
|
|
378
|
+
// Mousedown rather than click so the editor mounts before focus settles, and
|
|
379
|
+
// preventDefault so the browser doesn't move focus to <body> when the preview
|
|
380
|
+
// (the mousedown target) unmounts mid-click — that stray blur would otherwise
|
|
381
|
+
// immediately tear the new editor back down.
|
|
382
|
+
const beginEditingFromPointer = useCallback(
|
|
383
|
+
(event: ReactMouseEvent) => {
|
|
384
|
+
event.preventDefault();
|
|
385
|
+
beginEditing();
|
|
386
|
+
},
|
|
387
|
+
[beginEditing],
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
const endEditing = useCallback(() => setEditing(false), []);
|
|
391
|
+
|
|
392
|
+
if (editing) {
|
|
354
393
|
// Empty steps mounted eagerly (freshly inserted) auto-focus their title.
|
|
355
394
|
// A preview upgraded by a click focuses its field too, so a single click
|
|
356
|
-
// starts editing.
|
|
357
|
-
//
|
|
395
|
+
// starts editing. The editor tears back down to a preview when focus
|
|
396
|
+
// leaves the step (see TestStepContent's blur handling).
|
|
358
397
|
return (
|
|
359
398
|
<TestStepContent
|
|
360
399
|
block={block}
|
|
361
400
|
editor={editor}
|
|
362
401
|
stepNumber={stepNumber}
|
|
402
|
+
viewMode={viewMode}
|
|
363
403
|
autoFocusEnabled={isEmptyStep}
|
|
364
|
-
focusOnMount={
|
|
404
|
+
focusOnMount={focusOnMountRef.current}
|
|
405
|
+
onEditEnd={endEditing}
|
|
365
406
|
/>
|
|
366
407
|
);
|
|
367
408
|
}
|
|
368
409
|
|
|
369
410
|
return (
|
|
370
411
|
<div
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
412
|
+
className="bn-teststep-preview-wrapper"
|
|
413
|
+
tabIndex={0}
|
|
414
|
+
onMouseDownCapture={beginEditingFromPointer}
|
|
415
|
+
onFocusCapture={beginEditing}
|
|
374
416
|
>
|
|
375
417
|
<TestStepPreview
|
|
376
418
|
blockId={block.id}
|
|
377
419
|
stepNumber={stepNumber}
|
|
420
|
+
viewMode={viewMode}
|
|
378
421
|
stepTitle={(block.props.stepTitle as string) || ""}
|
|
379
422
|
stepData={(block.props.stepData as string) || ""}
|
|
380
423
|
expectedResult={(block.props.expectedResult as string) || ""}
|
|
@@ -387,14 +430,18 @@ function TestStepContent({
|
|
|
387
430
|
block,
|
|
388
431
|
editor,
|
|
389
432
|
stepNumber,
|
|
433
|
+
viewMode,
|
|
390
434
|
autoFocusEnabled = false,
|
|
391
435
|
focusOnMount = false,
|
|
436
|
+
onEditEnd,
|
|
392
437
|
}: {
|
|
393
438
|
block: any;
|
|
394
439
|
editor: any;
|
|
395
440
|
stepNumber: number;
|
|
441
|
+
viewMode: StepViewMode;
|
|
396
442
|
autoFocusEnabled?: boolean;
|
|
397
443
|
focusOnMount?: boolean;
|
|
444
|
+
onEditEnd?: () => void;
|
|
398
445
|
}) {
|
|
399
446
|
// When a preview is upgraded by a click, focus its primary field once on
|
|
400
447
|
// mount so a single click starts editing (caret at end).
|
|
@@ -411,12 +458,8 @@ function TestStepContent({
|
|
|
411
458
|
const [isDataVisible, setIsDataVisible] = useState(dataHasContent);
|
|
412
459
|
const [shouldFocusDataField, setShouldFocusDataField] = useState(false);
|
|
413
460
|
const uploadImage = useStepImageUpload();
|
|
414
|
-
const [viewMode, setViewMode] = useState<StepViewMode>(() => readStepViewMode());
|
|
415
461
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
416
462
|
const [forceVertical, setForceVertical] = useState(false);
|
|
417
|
-
// In compact mode each step collapses to a reading-focused row and only
|
|
418
|
-
// expands to the full editing layout while one of its fields has focus.
|
|
419
|
-
const [expanded, setExpanded] = useState(false);
|
|
420
463
|
|
|
421
464
|
useEffect(() => {
|
|
422
465
|
const el = containerRef.current?.parentElement;
|
|
@@ -432,29 +475,41 @@ function TestStepContent({
|
|
|
432
475
|
|
|
433
476
|
const compactMode = viewMode === "compact";
|
|
434
477
|
const effectiveHorizontal = viewMode === "horizontal" && !forceVertical;
|
|
435
|
-
//
|
|
436
|
-
//
|
|
437
|
-
|
|
438
|
-
|
|
478
|
+
// A mounted step is, by definition, the one being edited, so it always
|
|
479
|
+
// shows the full editing layout — the collapsed reading row is now the
|
|
480
|
+
// read-only preview's job.
|
|
481
|
+
const compactCollapsed = false;
|
|
482
|
+
|
|
483
|
+
// Tear the editor back down to the read-only preview once focus leaves the
|
|
484
|
+
// whole step. Re-checked on the next frame so transient blurs (clicking a
|
|
485
|
+
// toolbar button, or the link popover that portals to <body>) don't
|
|
486
|
+
// collapse an active edit. Edits persist to block props on change, so
|
|
487
|
+
// unmounting never loses data. Re-bound when the layout (and thus the root
|
|
488
|
+
// element) changes so it always listens on the live root.
|
|
439
489
|
useEffect(() => {
|
|
440
|
-
|
|
490
|
+
const root = containerRef.current;
|
|
491
|
+
if (!root || !onEditEnd) {
|
|
441
492
|
return;
|
|
442
493
|
}
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
494
|
+
const handleFocusOut = () => {
|
|
495
|
+
// Defer to the next frame so focus moving *within* the step (or to a
|
|
496
|
+
// popover that portals to <body>, e.g. the link editor) has settled
|
|
497
|
+
// before we decide whether editing has really ended.
|
|
498
|
+
requestAnimationFrame(() => {
|
|
499
|
+
const active = document.activeElement;
|
|
500
|
+
if (
|
|
501
|
+
active &&
|
|
502
|
+
(root.contains(active) ||
|
|
503
|
+
active.closest(".bn-popover-content, .bn-form-popover, [role='dialog']"))
|
|
504
|
+
) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
onEditEnd();
|
|
508
|
+
});
|
|
456
509
|
};
|
|
457
|
-
|
|
510
|
+
root.addEventListener("focusout", handleFocusOut);
|
|
511
|
+
return () => root.removeEventListener("focusout", handleFocusOut);
|
|
512
|
+
}, [onEditEnd, effectiveHorizontal]);
|
|
458
513
|
|
|
459
514
|
const combinedStepValue = useMemo(() => {
|
|
460
515
|
if (!stepData) {
|
|
@@ -588,34 +643,14 @@ function TestStepContent({
|
|
|
588
643
|
next = "vertical";
|
|
589
644
|
}
|
|
590
645
|
writeStepViewMode(next);
|
|
591
|
-
|
|
646
|
+
// The shared useStepViewMode hook (in every step, including this one)
|
|
647
|
+
// listens for this event and re-reads the mode, so we don't track it
|
|
648
|
+
// locally here.
|
|
592
649
|
if (typeof window !== "undefined") {
|
|
593
650
|
window.dispatchEvent(new Event("bn-step-view-mode"));
|
|
594
651
|
}
|
|
595
652
|
}, [viewMode, forceVertical]);
|
|
596
653
|
|
|
597
|
-
const handleContentFocusCapture = useCallback(() => {
|
|
598
|
-
if (viewMode === "compact") {
|
|
599
|
-
setExpanded(true);
|
|
600
|
-
}
|
|
601
|
-
}, [viewMode]);
|
|
602
|
-
|
|
603
|
-
const handleContentBlurCapture = useCallback(
|
|
604
|
-
(event: FocusEvent<HTMLDivElement>) => {
|
|
605
|
-
if (viewMode !== "compact") {
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
// Keep the step expanded while focus stays inside it (e.g. moving to a
|
|
609
|
-
// toolbar or action button); collapse only when focus leaves entirely.
|
|
610
|
-
const nextTarget = event.relatedTarget as Node | null;
|
|
611
|
-
if (nextTarget && event.currentTarget.contains(nextTarget)) {
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
setExpanded(false);
|
|
615
|
-
},
|
|
616
|
-
[viewMode],
|
|
617
|
-
);
|
|
618
|
-
|
|
619
654
|
const [dataFocusSignal] = useState(0);
|
|
620
655
|
const [expectedFocusSignal, setExpectedFocusSignal] = useState(0);
|
|
621
656
|
|
|
@@ -694,11 +729,7 @@ function TestStepContent({
|
|
|
694
729
|
<span className="bn-teststep__number">{stepNumber}</span>
|
|
695
730
|
<div className="bn-teststep__line" />
|
|
696
731
|
</div>
|
|
697
|
-
<div
|
|
698
|
-
className="bn-teststep__content"
|
|
699
|
-
onFocus={handleContentFocusCapture}
|
|
700
|
-
onBlur={handleContentBlurCapture}
|
|
701
|
-
>
|
|
732
|
+
<div className="bn-teststep__content">
|
|
702
733
|
<div className="bn-teststep__header">
|
|
703
734
|
{!compactMode && <span className="bn-teststep__title">Step</span>}
|
|
704
735
|
{viewToggleButton}
|