testomatio-editor-blocks 0.4.82 → 0.4.84
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 +92 -17
- package/package.json +1 -1
- package/src/editor/blocks/step.tsx +122 -45
|
@@ -313,23 +313,62 @@ function TestStepBlock({ block, editor }) {
|
|
|
313
313
|
focusOnMountRef.current = true;
|
|
314
314
|
setEditing(true);
|
|
315
315
|
}, []);
|
|
316
|
-
// Mousedown rather than click so the editor mounts before focus settles, and
|
|
317
|
-
// preventDefault so the browser doesn't move focus to <body> when the preview
|
|
318
|
-
// (the mousedown target) unmounts mid-click — that stray blur would otherwise
|
|
319
|
-
// immediately tear the new editor back down.
|
|
320
|
-
const beginEditingFromPointer = useCallback((event) => {
|
|
321
|
-
event.preventDefault();
|
|
322
|
-
beginEditing();
|
|
323
|
-
}, [beginEditing]);
|
|
324
316
|
const endEditing = useCallback(() => setEditing(false), []);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
317
|
+
// `editing` read from a ref so the single mousedown guard below (attached once)
|
|
318
|
+
// always sees the current state without re-binding.
|
|
319
|
+
const editingRef = useRef(editing);
|
|
320
|
+
editingRef.current = editing;
|
|
321
|
+
// Zero-box anchor (display: contents) used only to locate this block's node-view
|
|
322
|
+
// element; it adds no layout box, so the step renders exactly as before.
|
|
323
|
+
const anchorRef = useRef(null);
|
|
324
|
+
// The step is a ProseMirror atom node (content: "none") whose node-view
|
|
325
|
+
// (`.bn-block-content`) is wider than the visible step, so the empty area around
|
|
326
|
+
// it — and, in the collapsed preview, the whole strip to the right of the text —
|
|
327
|
+
// is inert atom surface. A plain mousedown there makes ProseMirror node-select the
|
|
328
|
+
// block (the stray blue outline) instead of editing it. One native listener on the
|
|
329
|
+
// node-view guards every mousedown in both the preview and editing states before
|
|
330
|
+
// ProseMirror sees it. Native (not onMouseDown) is required because React 18
|
|
331
|
+
// delegates at the app root, so its handlers run after ProseMirror's own.
|
|
332
|
+
useEffect(() => {
|
|
333
|
+
var _a;
|
|
334
|
+
const blockEl = (_a = anchorRef.current) === null || _a === void 0 ? void 0 : _a.closest(".bn-block-content");
|
|
335
|
+
if (!blockEl) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const handleMouseDown = (event) => {
|
|
339
|
+
const target = event.target;
|
|
340
|
+
// The real editor surface, form controls, and popovers/dialogs need the
|
|
341
|
+
// native mousedown (caret placement, typing, focus) to behave normally.
|
|
342
|
+
if (target === null || target === void 0 ? void 0 : target.closest('.overtype-container, textarea, input, [role="dialog"], .bn-popover-content, .bn-form-popover')) {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
// Buttons and links (toolbar, view toggle, action bar, autocomplete
|
|
346
|
+
// suggestions): preventDefault keeps the focused field from blurring — a blur
|
|
347
|
+
// node-selects the atom and can trip the focusout teardown on re-render — but
|
|
348
|
+
// we don't stopPropagation, so their own click/mousedown handlers still run.
|
|
349
|
+
if (target === null || target === void 0 ? void 0 : target.closest("button, a[href]")) {
|
|
350
|
+
event.preventDefault();
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
// Everything else — field labels, the timeline, the header, surrounding
|
|
354
|
+
// padding, and the empty node-view strip — is inert atom chrome. Swallow the
|
|
355
|
+
// mousedown so ProseMirror can't node-select the block; in the collapsed
|
|
356
|
+
// preview a click there means "edit this step", so begin editing.
|
|
357
|
+
event.preventDefault();
|
|
358
|
+
event.stopPropagation();
|
|
359
|
+
if (!editingRef.current) {
|
|
360
|
+
beginEditing();
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
blockEl.addEventListener("mousedown", handleMouseDown);
|
|
364
|
+
return () => blockEl.removeEventListener("mousedown", handleMouseDown);
|
|
365
|
+
}, [beginEditing]);
|
|
366
|
+
return (_jsx("div", { ref: anchorRef, style: { display: "contents" }, children: editing ? (
|
|
367
|
+
// Empty steps mounted eagerly (freshly inserted) auto-focus their title. A
|
|
368
|
+
// preview upgraded by a click focuses its field too, so a single click starts
|
|
369
|
+
// editing. The editor tears back down to a preview when focus leaves the step
|
|
370
|
+
// (see TestStepContent's blur handling).
|
|
371
|
+
_jsx(TestStepContent, { block: block, editor: editor, stepNumber: stepNumber, viewMode: viewMode, autoFocusEnabled: isEmptyStep, focusOnMount: focusOnMountRef.current, onEditEnd: endEditing })) : (_jsx("div", { className: "bn-teststep-preview-wrapper", tabIndex: 0, onFocusCapture: beginEditing, children: _jsx(TestStepPreview, { blockId: block.id, stepNumber: stepNumber, viewMode: viewMode, stepTitle: block.props.stepTitle || "", stepData: block.props.stepData || "", expectedResult: block.props.expectedResult || "" }) })) }));
|
|
333
372
|
}
|
|
334
373
|
function TestStepContent({ block, editor, stepNumber, viewMode, autoFocusEnabled = false, focusOnMount = false, onEditEnd, }) {
|
|
335
374
|
// When a preview is upgraded by a click, focus its primary field once on
|
|
@@ -347,9 +386,19 @@ function TestStepContent({ block, editor, stepNumber, viewMode, autoFocusEnabled
|
|
|
347
386
|
const uploadImage = useStepImageUpload();
|
|
348
387
|
const containerRef = useRef(null);
|
|
349
388
|
const [forceVertical, setForceVertical] = useState(false);
|
|
389
|
+
// Set for a moment while a view-mode toggle is in flight. Switching into or
|
|
390
|
+
// out of the horizontal layout swaps the whole subtree, so the OverType editor
|
|
391
|
+
// unmounts and its blur would otherwise trip the focusout teardown below and
|
|
392
|
+
// collapse the step to a preview. The flag tells the teardown to re-focus the
|
|
393
|
+
// freshly mounted editor instead of ending the edit.
|
|
394
|
+
const viewTransitionRef = useRef(false);
|
|
350
395
|
useEffect(() => {
|
|
351
396
|
var _a;
|
|
352
|
-
|
|
397
|
+
// Measure the block's node-view box (the step's real available width). The
|
|
398
|
+
// immediate parent is a `display: contents` anchor with no box, so its width
|
|
399
|
+
// reads as 0 and would wrongly force the vertical layout — in horizontal view
|
|
400
|
+
// that spurious flip remounts the editor and collapses the step on open.
|
|
401
|
+
const el = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.closest(".bn-block-content");
|
|
353
402
|
if (!el)
|
|
354
403
|
return;
|
|
355
404
|
const observer = new ResizeObserver((entries) => {
|
|
@@ -382,12 +431,24 @@ function TestStepContent({ block, editor, stepNumber, viewMode, autoFocusEnabled
|
|
|
382
431
|
// popover that portals to <body>, e.g. the link editor) has settled
|
|
383
432
|
// before we decide whether editing has really ended.
|
|
384
433
|
requestAnimationFrame(() => {
|
|
434
|
+
var _a;
|
|
385
435
|
const active = document.activeElement;
|
|
386
436
|
if (active &&
|
|
387
437
|
(root.contains(active) ||
|
|
388
438
|
active.closest(".bn-popover-content, .bn-form-popover, [role='dialog']"))) {
|
|
389
439
|
return;
|
|
390
440
|
}
|
|
441
|
+
// A view-mode toggle remounted the editor; the blur isn't the user
|
|
442
|
+
// leaving the step. Keep editing and move focus into the freshly
|
|
443
|
+
// mounted layout instead of collapsing to a preview.
|
|
444
|
+
if (viewTransitionRef.current) {
|
|
445
|
+
viewTransitionRef.current = false;
|
|
446
|
+
const live = (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.querySelector("textarea");
|
|
447
|
+
if (live) {
|
|
448
|
+
live.focus();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
391
452
|
onEditEnd();
|
|
392
453
|
});
|
|
393
454
|
};
|
|
@@ -502,6 +563,20 @@ function TestStepContent({ block, editor, stepNumber, viewMode, autoFocusEnabled
|
|
|
502
563
|
else {
|
|
503
564
|
next = "vertical";
|
|
504
565
|
}
|
|
566
|
+
// Only switching into or out of the horizontal layout swaps the subtree and
|
|
567
|
+
// remounts the editor; vertical↔compact reuse the same editor instance and
|
|
568
|
+
// never blur. Flag just the remounting transitions so the focusout teardown
|
|
569
|
+
// re-focuses the new editor instead of collapsing — and so a genuine
|
|
570
|
+
// click-away after a non-remounting toggle still tears down normally. Cleared
|
|
571
|
+
// on a timer as a safety net in case the expected blur never arrives.
|
|
572
|
+
if (viewMode === "horizontal" || next === "horizontal") {
|
|
573
|
+
viewTransitionRef.current = true;
|
|
574
|
+
if (typeof window !== "undefined") {
|
|
575
|
+
window.setTimeout(() => {
|
|
576
|
+
viewTransitionRef.current = false;
|
|
577
|
+
}, 300);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
505
580
|
writeStepViewMode(next);
|
|
506
581
|
// The shared useStepViewMode hook (in every step, including this one)
|
|
507
582
|
// listens for this event and re-reads the mode, so we don't track it
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createReactBlockSpec, useEditorChange } from "@blocknote/react";
|
|
2
|
-
import { useCallback, useEffect, useMemo, useRef, useState
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { StepField, StepFieldPreview } from "./stepField";
|
|
4
4
|
import { StepHorizontalView } from "./stepHorizontalView";
|
|
5
5
|
import { useStepImageUpload } from "../stepImageUpload";
|
|
@@ -375,53 +375,95 @@ function TestStepBlock({ block, editor }: { block: any; editor: any }) {
|
|
|
375
375
|
setEditing(true);
|
|
376
376
|
}, []);
|
|
377
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
378
|
const endEditing = useCallback(() => setEditing(false), []);
|
|
391
379
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
380
|
+
// `editing` read from a ref so the single mousedown guard below (attached once)
|
|
381
|
+
// always sees the current state without re-binding.
|
|
382
|
+
const editingRef = useRef(editing);
|
|
383
|
+
editingRef.current = editing;
|
|
384
|
+
|
|
385
|
+
// Zero-box anchor (display: contents) used only to locate this block's node-view
|
|
386
|
+
// element; it adds no layout box, so the step renders exactly as before.
|
|
387
|
+
const anchorRef = useRef<HTMLDivElement>(null);
|
|
388
|
+
|
|
389
|
+
// The step is a ProseMirror atom node (content: "none") whose node-view
|
|
390
|
+
// (`.bn-block-content`) is wider than the visible step, so the empty area around
|
|
391
|
+
// it — and, in the collapsed preview, the whole strip to the right of the text —
|
|
392
|
+
// is inert atom surface. A plain mousedown there makes ProseMirror node-select the
|
|
393
|
+
// block (the stray blue outline) instead of editing it. One native listener on the
|
|
394
|
+
// node-view guards every mousedown in both the preview and editing states before
|
|
395
|
+
// ProseMirror sees it. Native (not onMouseDown) is required because React 18
|
|
396
|
+
// delegates at the app root, so its handlers run after ProseMirror's own.
|
|
397
|
+
useEffect(() => {
|
|
398
|
+
const blockEl = anchorRef.current?.closest(".bn-block-content") as HTMLElement | null;
|
|
399
|
+
if (!blockEl) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const handleMouseDown = (event: MouseEvent) => {
|
|
403
|
+
const target = event.target as HTMLElement | null;
|
|
404
|
+
// The real editor surface, form controls, and popovers/dialogs need the
|
|
405
|
+
// native mousedown (caret placement, typing, focus) to behave normally.
|
|
406
|
+
if (
|
|
407
|
+
target?.closest(
|
|
408
|
+
'.overtype-container, textarea, input, [role="dialog"], .bn-popover-content, .bn-form-popover',
|
|
409
|
+
)
|
|
410
|
+
) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
// Buttons and links (toolbar, view toggle, action bar, autocomplete
|
|
414
|
+
// suggestions): preventDefault keeps the focused field from blurring — a blur
|
|
415
|
+
// node-selects the atom and can trip the focusout teardown on re-render — but
|
|
416
|
+
// we don't stopPropagation, so their own click/mousedown handlers still run.
|
|
417
|
+
if (target?.closest("button, a[href]")) {
|
|
418
|
+
event.preventDefault();
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
// Everything else — field labels, the timeline, the header, surrounding
|
|
422
|
+
// padding, and the empty node-view strip — is inert atom chrome. Swallow the
|
|
423
|
+
// mousedown so ProseMirror can't node-select the block; in the collapsed
|
|
424
|
+
// preview a click there means "edit this step", so begin editing.
|
|
425
|
+
event.preventDefault();
|
|
426
|
+
event.stopPropagation();
|
|
427
|
+
if (!editingRef.current) {
|
|
428
|
+
beginEditing();
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
blockEl.addEventListener("mousedown", handleMouseDown);
|
|
432
|
+
return () => blockEl.removeEventListener("mousedown", handleMouseDown);
|
|
433
|
+
}, [beginEditing]);
|
|
409
434
|
|
|
410
435
|
return (
|
|
411
|
-
<div
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
436
|
+
<div ref={anchorRef} style={{ display: "contents" }}>
|
|
437
|
+
{editing ? (
|
|
438
|
+
// Empty steps mounted eagerly (freshly inserted) auto-focus their title. A
|
|
439
|
+
// preview upgraded by a click focuses its field too, so a single click starts
|
|
440
|
+
// editing. The editor tears back down to a preview when focus leaves the step
|
|
441
|
+
// (see TestStepContent's blur handling).
|
|
442
|
+
<TestStepContent
|
|
443
|
+
block={block}
|
|
444
|
+
editor={editor}
|
|
445
|
+
stepNumber={stepNumber}
|
|
446
|
+
viewMode={viewMode}
|
|
447
|
+
autoFocusEnabled={isEmptyStep}
|
|
448
|
+
focusOnMount={focusOnMountRef.current}
|
|
449
|
+
onEditEnd={endEditing}
|
|
450
|
+
/>
|
|
451
|
+
) : (
|
|
452
|
+
<div
|
|
453
|
+
className="bn-teststep-preview-wrapper"
|
|
454
|
+
tabIndex={0}
|
|
455
|
+
onFocusCapture={beginEditing}
|
|
456
|
+
>
|
|
457
|
+
<TestStepPreview
|
|
458
|
+
blockId={block.id}
|
|
459
|
+
stepNumber={stepNumber}
|
|
460
|
+
viewMode={viewMode}
|
|
461
|
+
stepTitle={(block.props.stepTitle as string) || ""}
|
|
462
|
+
stepData={(block.props.stepData as string) || ""}
|
|
463
|
+
expectedResult={(block.props.expectedResult as string) || ""}
|
|
464
|
+
/>
|
|
465
|
+
</div>
|
|
466
|
+
)}
|
|
425
467
|
</div>
|
|
426
468
|
);
|
|
427
469
|
}
|
|
@@ -460,9 +502,19 @@ function TestStepContent({
|
|
|
460
502
|
const uploadImage = useStepImageUpload();
|
|
461
503
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
462
504
|
const [forceVertical, setForceVertical] = useState(false);
|
|
505
|
+
// Set for a moment while a view-mode toggle is in flight. Switching into or
|
|
506
|
+
// out of the horizontal layout swaps the whole subtree, so the OverType editor
|
|
507
|
+
// unmounts and its blur would otherwise trip the focusout teardown below and
|
|
508
|
+
// collapse the step to a preview. The flag tells the teardown to re-focus the
|
|
509
|
+
// freshly mounted editor instead of ending the edit.
|
|
510
|
+
const viewTransitionRef = useRef(false);
|
|
463
511
|
|
|
464
512
|
useEffect(() => {
|
|
465
|
-
|
|
513
|
+
// Measure the block's node-view box (the step's real available width). The
|
|
514
|
+
// immediate parent is a `display: contents` anchor with no box, so its width
|
|
515
|
+
// reads as 0 and would wrongly force the vertical layout — in horizontal view
|
|
516
|
+
// that spurious flip remounts the editor and collapses the step on open.
|
|
517
|
+
const el = containerRef.current?.closest(".bn-block-content");
|
|
466
518
|
if (!el) return;
|
|
467
519
|
const observer = new ResizeObserver((entries) => {
|
|
468
520
|
for (const entry of entries) {
|
|
@@ -504,6 +556,17 @@ function TestStepContent({
|
|
|
504
556
|
) {
|
|
505
557
|
return;
|
|
506
558
|
}
|
|
559
|
+
// A view-mode toggle remounted the editor; the blur isn't the user
|
|
560
|
+
// leaving the step. Keep editing and move focus into the freshly
|
|
561
|
+
// mounted layout instead of collapsing to a preview.
|
|
562
|
+
if (viewTransitionRef.current) {
|
|
563
|
+
viewTransitionRef.current = false;
|
|
564
|
+
const live = containerRef.current?.querySelector("textarea");
|
|
565
|
+
if (live) {
|
|
566
|
+
(live as HTMLTextAreaElement).focus();
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
507
570
|
onEditEnd();
|
|
508
571
|
});
|
|
509
572
|
};
|
|
@@ -642,6 +705,20 @@ function TestStepContent({
|
|
|
642
705
|
} else {
|
|
643
706
|
next = "vertical";
|
|
644
707
|
}
|
|
708
|
+
// Only switching into or out of the horizontal layout swaps the subtree and
|
|
709
|
+
// remounts the editor; vertical↔compact reuse the same editor instance and
|
|
710
|
+
// never blur. Flag just the remounting transitions so the focusout teardown
|
|
711
|
+
// re-focuses the new editor instead of collapsing — and so a genuine
|
|
712
|
+
// click-away after a non-remounting toggle still tears down normally. Cleared
|
|
713
|
+
// on a timer as a safety net in case the expected blur never arrives.
|
|
714
|
+
if (viewMode === "horizontal" || next === "horizontal") {
|
|
715
|
+
viewTransitionRef.current = true;
|
|
716
|
+
if (typeof window !== "undefined") {
|
|
717
|
+
window.setTimeout(() => {
|
|
718
|
+
viewTransitionRef.current = false;
|
|
719
|
+
}, 300);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
645
722
|
writeStepViewMode(next);
|
|
646
723
|
// The shared useStepViewMode hook (in every step, including this one)
|
|
647
724
|
// listens for this event and re-reads the mode, so we don't track it
|