@parhelia/core 0.1.12883 → 0.1.12886
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/config/config.js +29 -1
- package/dist/config/config.js.map +1 -1
- package/dist/editor/LinkEditorDialog.js +25 -25
- package/dist/editor/LinkEditorDialog.js.map +1 -1
- package/dist/editor/client/EditorShell.js +1 -0
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/operations.d.ts +2 -1
- package/dist/editor/client/operations.js +61 -5
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/field-types/richtext/components/ReactSlate.js +42 -32
- package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
- package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.js +25 -1
- package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.js.map +1 -1
- package/dist/editor/menubar/VersionSelector.js +16 -1
- package/dist/editor/menubar/VersionSelector.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +213 -75
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.js +80 -32
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +77 -0
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/reviews/Comments.js +7 -0
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/SuggestedEdit.js +9 -4
- package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
- package/dist/editor/reviews/suggestionContentChange.d.ts +2 -0
- package/dist/editor/reviews/suggestionContentChange.js +9 -0
- package/dist/editor/reviews/suggestionContentChange.js.map +1 -0
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/package.json +1 -1
|
@@ -355,6 +355,16 @@ export function deriveSelectorState(params) {
|
|
|
355
355
|
}
|
|
356
356
|
return "disabled";
|
|
357
357
|
}
|
|
358
|
+
// Finishers for every highlight overlay currently on screen. Lets a superseding
|
|
359
|
+
// reveal wipe the previous reveal's highlight immediately so it does not keep
|
|
360
|
+
// pulsing over the close/re-open transition (the "uncoordinated animation").
|
|
361
|
+
const activeHighlightFinishers = new Set();
|
|
362
|
+
// Remove all active highlight overlays right now (resolving their lifetimes).
|
|
363
|
+
function clearActiveHighlightOverlays() {
|
|
364
|
+
for (const finish of [...activeHighlightFinishers]) {
|
|
365
|
+
finish();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
358
368
|
// Create a temporary highlight overlay. Resolves when its finite "life"
|
|
359
369
|
// animation ends and the overlay has been removed (event-driven, not a timer).
|
|
360
370
|
function showHighlightOverlay(x, y, width, height, duration) {
|
|
@@ -415,6 +425,7 @@ function showHighlightOverlay(x, y, width, height, duration) {
|
|
|
415
425
|
if (finished)
|
|
416
426
|
return;
|
|
417
427
|
finished = true;
|
|
428
|
+
activeHighlightFinishers.delete(finish);
|
|
418
429
|
overlay.removeEventListener("animationend", onAnimationEnd);
|
|
419
430
|
overlay.remove();
|
|
420
431
|
resolve();
|
|
@@ -426,6 +437,8 @@ function showHighlightOverlay(x, y, width, height, duration) {
|
|
|
426
437
|
}
|
|
427
438
|
};
|
|
428
439
|
overlay.addEventListener("animationend", onAnimationEnd);
|
|
440
|
+
// Allow a superseding reveal to wipe this overlay immediately.
|
|
441
|
+
activeHighlightFinishers.add(finish);
|
|
429
442
|
// Guardrail: if animationend is ever missed, remove shortly after duration.
|
|
430
443
|
window.setTimeout(finish, duration + 500);
|
|
431
444
|
});
|
|
@@ -463,13 +476,80 @@ function clearActiveReveal(id) {
|
|
|
463
476
|
activeRevealController = null;
|
|
464
477
|
}
|
|
465
478
|
}
|
|
479
|
+
// The surface key for the Agents panel depends on HOW it opens (mirrors the
|
|
480
|
+
// open logic in config.tsx `openAgentsPanel`): the dedicated far-right panel on
|
|
481
|
+
// desktop ("agents-panel") vs. a left sidebar stack on mobile / contexts without
|
|
482
|
+
// `setShowAgentsPanel` ("sidebar:agents-panel"). Used for de-duplication within
|
|
483
|
+
// a reveal and to keep the surface key self-documenting.
|
|
484
|
+
function agentsPanelSurfaceKey(editContext) {
|
|
485
|
+
if (editContext &&
|
|
486
|
+
!editContext.isMobile &&
|
|
487
|
+
typeof editContext.setShowAgentsPanel === "function") {
|
|
488
|
+
return "agents-panel";
|
|
489
|
+
}
|
|
490
|
+
return "sidebar:agents-panel";
|
|
491
|
+
}
|
|
466
492
|
// Close the More-panels flyout if it is currently open (inverse of open-more-sidebars).
|
|
493
|
+
function isMorePanelsExpanded() {
|
|
494
|
+
return !!document.querySelector('[data-testid="more-sidebars-button"][aria-expanded="true"]');
|
|
495
|
+
}
|
|
467
496
|
function closeMorePanelsIfOpen() {
|
|
468
|
-
if (!
|
|
497
|
+
if (!isMorePanelsExpanded())
|
|
469
498
|
return;
|
|
470
499
|
const trigger = document.querySelector('[data-testid="more-sidebars-button"]');
|
|
471
500
|
trigger?.click();
|
|
472
501
|
}
|
|
502
|
+
function closeVersionSelectorIfOpen() {
|
|
503
|
+
if (!isElementPresent("@version-selector-list"))
|
|
504
|
+
return;
|
|
505
|
+
const list = document.querySelector('[data-testid="version-selector-list"]');
|
|
506
|
+
if (list) {
|
|
507
|
+
list.dispatchEvent(new KeyboardEvent("keydown", {
|
|
508
|
+
key: "Escape",
|
|
509
|
+
code: "Escape",
|
|
510
|
+
bubbles: true,
|
|
511
|
+
cancelable: true,
|
|
512
|
+
}));
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const trigger = document.querySelector('[data-testid="version-selector"][aria-expanded="true"]') ??
|
|
516
|
+
document.querySelector('[data-testid="version-selector"]');
|
|
517
|
+
trigger?.click();
|
|
518
|
+
}
|
|
519
|
+
// Resolve once the More-panels popover is fully closed: its list is gone AND
|
|
520
|
+
// the trigger no longer reports aria-expanded="true". This lets a superseding
|
|
521
|
+
// More-panels reveal reopen from a truly closed state instead of sampling
|
|
522
|
+
// present-but-closing menu items.
|
|
523
|
+
async function waitForMorePanelsClosed(timeoutMs = 1000) {
|
|
524
|
+
const isStillOpen = () => isElementPresent("@more-sidebars-panel") || isMorePanelsExpanded();
|
|
525
|
+
const waitUntilClosed = async () => {
|
|
526
|
+
const startedAt = Date.now();
|
|
527
|
+
while (Date.now() - startedAt < timeoutMs && isStillOpen()) {
|
|
528
|
+
await waitForDelay(50);
|
|
529
|
+
}
|
|
530
|
+
return !isStillOpen();
|
|
531
|
+
};
|
|
532
|
+
if (await waitUntilClosed())
|
|
533
|
+
return;
|
|
534
|
+
// If the first close signal was missed, try one guarded close again. The
|
|
535
|
+
// guard prevents toggling a panel that is already closing or closed.
|
|
536
|
+
closeMorePanelsIfOpen();
|
|
537
|
+
await waitForSelectorAnimations('[data-testid="more-sidebars-panel"]');
|
|
538
|
+
await waitUntilClosed();
|
|
539
|
+
}
|
|
540
|
+
// Resolve once the version-selector popover is fully closed: its list is gone
|
|
541
|
+
// AND the trigger no longer reports aria-expanded="true". The supersede close
|
|
542
|
+
// awaits this so the next reveal's open-version-selector (which early-returns
|
|
543
|
+
// when it believes the popover is still open/visible) always starts from a
|
|
544
|
+
// truly closed state, making the close-then-reopen deterministic.
|
|
545
|
+
async function waitForVersionSelectorClosed(timeoutMs = 1000) {
|
|
546
|
+
const startedAt = Date.now();
|
|
547
|
+
const isStillOpen = () => isElementPresent("@version-selector-list") ||
|
|
548
|
+
!!document.querySelector('[data-testid="version-selector"][aria-expanded="true"]');
|
|
549
|
+
while (Date.now() - startedAt < timeoutMs && isStillOpen()) {
|
|
550
|
+
await waitForDelay(50);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
473
553
|
function isAgentsPanelOpen() {
|
|
474
554
|
const trigger = document.querySelector('[data-testid="agents-panel-button"]');
|
|
475
555
|
return (trigger?.getAttribute("aria-pressed") === "true" ||
|
|
@@ -529,21 +609,32 @@ function SelectorButton({ selectorDef, keyProp, }) {
|
|
|
529
609
|
revealRef.current = null;
|
|
530
610
|
setIsActiveReveal(false);
|
|
531
611
|
}, []);
|
|
532
|
-
//
|
|
533
|
-
//
|
|
612
|
+
// Fully tear down this reveal: wipe its highlight, close every owned surface
|
|
613
|
+
// (reverse open order so nested surfaces close first) and reset the button.
|
|
614
|
+
// Used for explicit close (2nd click, auto timer), external-close handling,
|
|
615
|
+
// and being superseded by another reveal.
|
|
534
616
|
const runRestore = useCallback(async () => {
|
|
535
617
|
const reveal = revealRef.current;
|
|
618
|
+
// Remove the highlight immediately so it does not keep pulsing over the
|
|
619
|
+
// close/re-open transition while the next reveal builds up.
|
|
620
|
+
clearActiveHighlightOverlays();
|
|
536
621
|
// Reset button state immediately (button turns blue) while the surface
|
|
537
|
-
// teardown animation plays out; the
|
|
622
|
+
// teardown animation plays out; the promise resolves once settled.
|
|
538
623
|
clearActiveReveal(reveal?.id ?? controllerIdRef.current);
|
|
539
624
|
revealRef.current = null;
|
|
540
625
|
setIsActiveReveal(false);
|
|
541
|
-
if (reveal)
|
|
626
|
+
if (!reveal)
|
|
627
|
+
return;
|
|
628
|
+
// Close in reverse open order (nested surfaces close first).
|
|
629
|
+
for (let i = reveal.surfaceOps.length - 1; i >= 0; i -= 1) {
|
|
630
|
+
const op = reveal.surfaceOps[i];
|
|
631
|
+
if (!op)
|
|
632
|
+
continue;
|
|
542
633
|
try {
|
|
543
|
-
await
|
|
634
|
+
await op.close();
|
|
544
635
|
}
|
|
545
636
|
catch {
|
|
546
|
-
// ignore
|
|
637
|
+
// ignore individual close failures
|
|
547
638
|
}
|
|
548
639
|
}
|
|
549
640
|
}, []);
|
|
@@ -630,7 +721,7 @@ function SelectorButton({ selectorDef, keyProp, }) {
|
|
|
630
721
|
: isDisabled
|
|
631
722
|
? selectorDef.notFoundMessage || selectorDef.description
|
|
632
723
|
: selectorDef.description;
|
|
633
|
-
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { "aria-label": isActiveReveal ? CLOSE_PANE_LABEL : "Show me", onClick: async () => {
|
|
724
|
+
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { "data-manual-show-me": "true", "data-manual-show-me-before-action": selectorDef.beforeAction || undefined, "aria-label": isActiveReveal ? CLOSE_PANE_LABEL : "Show me", onClick: async () => {
|
|
634
725
|
// Second click while active -> restore (close=click behaviour).
|
|
635
726
|
if (isActiveReveal) {
|
|
636
727
|
runRestore();
|
|
@@ -658,16 +749,17 @@ function SelectorButton({ selectorDef, keyProp, }) {
|
|
|
658
749
|
return;
|
|
659
750
|
}
|
|
660
751
|
// State-machine semantics: any new Show-me action supersedes the
|
|
661
|
-
// currently active State 3 reveal from another button,
|
|
662
|
-
//
|
|
663
|
-
//
|
|
664
|
-
//
|
|
665
|
-
//
|
|
752
|
+
// currently active State 3 reveal from another button, fully
|
|
753
|
+
// tearing it down (clearing its highlight and closing its surfaces)
|
|
754
|
+
// BEFORE this reveal starts opening. Await the teardown so the close
|
|
755
|
+
// animation completes first - the transition is a clean, sequenced
|
|
756
|
+
// close-then-open - and so this reveal samples fresh DOM and avoids
|
|
757
|
+
// a stale "was this surface open?" read. Because clicks are
|
|
758
|
+
// serialized above, the superseded controller is always the
|
|
759
|
+
// previous fully-settled reveal (no race here).
|
|
666
760
|
const superseded = supersedeActiveReveal(controllerIdRef.current);
|
|
667
761
|
if (superseded) {
|
|
668
762
|
try {
|
|
669
|
-
// Await the close so A's surface finishes its teardown
|
|
670
|
-
// animation before B starts revealing (event-driven, not timed).
|
|
671
763
|
await superseded.close();
|
|
672
764
|
}
|
|
673
765
|
catch {
|
|
@@ -694,28 +786,40 @@ function SelectorButton({ selectorDef, keyProp, }) {
|
|
|
694
786
|
};
|
|
695
787
|
await action(actionProps);
|
|
696
788
|
};
|
|
697
|
-
//
|
|
698
|
-
//
|
|
699
|
-
|
|
700
|
-
const
|
|
789
|
+
// Surfaces this reveal opens, in open order. Closed in reverse on
|
|
790
|
+
// restore. The previous reveal was fully torn down above, so we
|
|
791
|
+
// always start from an empty set.
|
|
792
|
+
const surfaceOps = [];
|
|
793
|
+
// Register a surface unless one with the same key was already
|
|
794
|
+
// pushed by an earlier step in this same reveal.
|
|
795
|
+
const pushSurface = (op) => {
|
|
796
|
+
if (surfaceOps.some((existing) => existing.key === op.key)) {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
surfaceOps.push(op);
|
|
800
|
+
};
|
|
701
801
|
// 1. Sidebar-only: open the sidebar.
|
|
702
802
|
if (isSidebarOnly &&
|
|
703
803
|
editContext?.openSidebar &&
|
|
704
804
|
selectorDef.location) {
|
|
705
805
|
const sidebarId = selectorDef.location;
|
|
806
|
+
const surfaceKey = `sidebar:${sidebarId}`;
|
|
706
807
|
const wasOpen = !!editContext.openSidebars?.includes(sidebarId);
|
|
707
808
|
if (!wasOpen) {
|
|
708
809
|
editContext.openSidebar(sidebarId);
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
810
|
+
pushSurface({
|
|
811
|
+
key: surfaceKey,
|
|
812
|
+
close: async () => {
|
|
813
|
+
editContextRef.current?.toggleSidebar?.(sidebarId, {
|
|
814
|
+
forceClose: true,
|
|
815
|
+
});
|
|
816
|
+
await editContextRef.current?.waitForSurfaceSettled?.(`sidebar:${sidebarId}`);
|
|
817
|
+
},
|
|
818
|
+
isOpen: () => !!editContextRef.current?.openSidebars?.includes(sidebarId),
|
|
714
819
|
});
|
|
715
820
|
// Wait for the sidebar's open animation to finish before querying.
|
|
716
821
|
await editContext.waitForSurfaceSettled?.(`sidebar:${sidebarId}`);
|
|
717
822
|
}
|
|
718
|
-
openPredicates.push(() => !!editContextRef.current?.openSidebars?.includes(sidebarId));
|
|
719
823
|
}
|
|
720
824
|
const availabilityElements = anchorSelector
|
|
721
825
|
? resolveElements(anchorSelector)
|
|
@@ -734,11 +838,14 @@ function SelectorButton({ selectorDef, keyProp, }) {
|
|
|
734
838
|
if (shouldOpenEditorForm && editContext) {
|
|
735
839
|
const slotId = editContext.getActiveSlotId();
|
|
736
840
|
editContext.setEditorFormHiddenForSlot(slotId, false);
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
841
|
+
pushSurface({
|
|
842
|
+
key: "editor-form",
|
|
843
|
+
close: async () => {
|
|
844
|
+
editContextRef.current?.setEditorFormHiddenForSlot(slotId, true);
|
|
845
|
+
await editContextRef.current?.waitForSurfaceSettled?.("editor-form");
|
|
846
|
+
},
|
|
847
|
+
isOpen: () => !isEditorFormSliderHidden(getEditorFormSlider()),
|
|
740
848
|
});
|
|
741
|
-
openPredicates.push(() => !isEditorFormSliderHidden(getEditorFormSlider()));
|
|
742
849
|
await editContext.waitForSurfaceSettled?.("editor-form");
|
|
743
850
|
}
|
|
744
851
|
else if (selectorDef.tab) {
|
|
@@ -752,6 +859,7 @@ function SelectorButton({ selectorDef, keyProp, }) {
|
|
|
752
859
|
? resolvedElements
|
|
753
860
|
: availabilityElements;
|
|
754
861
|
const morePanelsWasOpen = isElementPresent("@more-sidebars-panel");
|
|
862
|
+
const versionSelectorWasOpen = isElementPresent("@version-selector-list");
|
|
755
863
|
// Capture pre-reveal state for the AI Assistant reveals so we only
|
|
756
864
|
// restore (close) what THIS button actually opened.
|
|
757
865
|
const agentsPanelWasOpen = isAgentsPanelOpen();
|
|
@@ -762,11 +870,32 @@ function SelectorButton({ selectorDef, keyProp, }) {
|
|
|
762
870
|
if (selectorDef.beforeAction === "open-more-sidebars") {
|
|
763
871
|
const morePanelsNowOpen = isElementPresent("@more-sidebars-panel");
|
|
764
872
|
if (!morePanelsWasOpen && morePanelsNowOpen) {
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
873
|
+
pushSurface({
|
|
874
|
+
key: "more-sidebars-panel",
|
|
875
|
+
close: async () => {
|
|
876
|
+
closeMorePanelsIfOpen();
|
|
877
|
+
await waitForSelectorAnimations('[data-testid="more-sidebars-panel"]');
|
|
878
|
+
await waitForMorePanelsClosed();
|
|
879
|
+
},
|
|
880
|
+
isOpen: () => isElementPresent("@more-sidebars-panel"),
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
if (selectorDef.beforeAction === "open-version-selector") {
|
|
885
|
+
const versionSelectorNowOpen = isElementPresent("@version-selector-list");
|
|
886
|
+
if (!versionSelectorWasOpen && versionSelectorNowOpen) {
|
|
887
|
+
pushSurface({
|
|
888
|
+
key: "version-selector",
|
|
889
|
+
close: async () => {
|
|
890
|
+
closeVersionSelectorIfOpen();
|
|
891
|
+
await waitForSelectorAnimations('[data-testid="version-selector-list"]');
|
|
892
|
+
// Gate until the popover is truly gone so the next
|
|
893
|
+
// reveal's open-version-selector reopens from a closed
|
|
894
|
+
// state (deterministic close-then-reopen).
|
|
895
|
+
await waitForVersionSelectorClosed();
|
|
896
|
+
},
|
|
897
|
+
isOpen: () => isElementPresent("@version-selector-list"),
|
|
768
898
|
});
|
|
769
|
-
openPredicates.push(() => isElementPresent("@more-sidebars-panel"));
|
|
770
899
|
}
|
|
771
900
|
}
|
|
772
901
|
if (selectorDef.beforeAction === "open-agents-panel" ||
|
|
@@ -775,46 +904,59 @@ function SelectorButton({ selectorDef, keyProp, }) {
|
|
|
775
904
|
// animation (see config openAgentsPanel/openAgentSettings), so
|
|
776
905
|
// no polling here.
|
|
777
906
|
const agentsPanelNowOpen = isAgentsPanelOpen();
|
|
778
|
-
// Push the panel
|
|
779
|
-
//
|
|
907
|
+
// Push the panel surface first so that, since surfaces close in
|
|
908
|
+
// reverse, the settings popover is closed before the panel.
|
|
780
909
|
if (!agentsPanelWasOpen && agentsPanelNowOpen) {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
910
|
+
pushSurface({
|
|
911
|
+
key: agentsPanelSurfaceKey(editContext),
|
|
912
|
+
close: async () => {
|
|
913
|
+
if (editContextRef.current?.isMobile) {
|
|
914
|
+
editContextRef.current?.toggleSidebar?.("agents-panel", {
|
|
915
|
+
forceClose: true,
|
|
916
|
+
});
|
|
917
|
+
await editContextRef.current?.waitForSurfaceSettled?.("sidebar:agents-panel");
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
editContextRef.current?.setShowAgentsPanel?.(false);
|
|
921
|
+
await editContextRef.current?.waitForSurfaceSettled?.("agents-panel");
|
|
922
|
+
}
|
|
923
|
+
},
|
|
924
|
+
isOpen: () => isAgentsPanelOpen(),
|
|
792
925
|
});
|
|
793
|
-
openPredicates.push(() => isAgentsPanelOpen());
|
|
794
926
|
}
|
|
795
927
|
if (selectorDef.beforeAction === "open-agent-settings") {
|
|
796
928
|
const settingsTargetNowOpen = !!selectorDef.selector &&
|
|
797
929
|
isElementPresent(selectorDef.selector);
|
|
798
930
|
if (!agentSettingsWasOpen && settingsTargetNowOpen) {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
931
|
+
pushSurface({
|
|
932
|
+
key: "agent-settings",
|
|
933
|
+
close: async () => {
|
|
934
|
+
const trigger = document.querySelector('[data-testid="agent-settings-popover-trigger"]');
|
|
935
|
+
if (trigger &&
|
|
936
|
+
selectorDef.selector &&
|
|
937
|
+
isElementPresent(selectorDef.selector)) {
|
|
938
|
+
trigger.click();
|
|
939
|
+
await waitForSelectorAnimations('[data-testid="agent-settings-popover-content"]');
|
|
940
|
+
}
|
|
941
|
+
},
|
|
942
|
+
isOpen: () => selectorDef.selector
|
|
943
|
+
? isElementPresent(selectorDef.selector)
|
|
944
|
+
: false,
|
|
807
945
|
});
|
|
808
|
-
openPredicates.push(() => selectorDef.selector
|
|
809
|
-
? isElementPresent(selectorDef.selector)
|
|
810
|
-
: false);
|
|
811
946
|
}
|
|
812
947
|
}
|
|
813
948
|
}
|
|
814
|
-
// afterAction is the declared inverse for custom reveals (e.g.
|
|
949
|
+
// afterAction is the declared inverse for custom reveals (e.g.
|
|
950
|
+
// field actions). It is a restore-only step with no open surface,
|
|
951
|
+
// so it uses a "custom:" key and is excluded from the still-open
|
|
952
|
+
// check below.
|
|
815
953
|
if (selectorDef.afterAction) {
|
|
816
|
-
|
|
817
|
-
|
|
954
|
+
pushSurface({
|
|
955
|
+
key: "custom:afterAction",
|
|
956
|
+
close: async () => {
|
|
957
|
+
await runManualAction(selectorDef.afterAction, !isSidebarOnly ? resolveElements() : availabilityElements);
|
|
958
|
+
},
|
|
959
|
+
isOpen: () => true,
|
|
818
960
|
});
|
|
819
961
|
}
|
|
820
962
|
// Highlight the target. For sidebar-only selectors there is no
|
|
@@ -838,26 +980,22 @@ function SelectorButton({ selectorDef, keyProp, }) {
|
|
|
838
980
|
highlightLifetime = highlightElement(highlightSelector, HIGHLIGHT_DURATION_MS);
|
|
839
981
|
}
|
|
840
982
|
}
|
|
841
|
-
// If we opened
|
|
983
|
+
// If we opened any surface, this was a State-3 reveal: keep the
|
|
842
984
|
// button active (green) and register it for mutual exclusion.
|
|
843
|
-
if (
|
|
985
|
+
if (surfaceOps.length > 0) {
|
|
844
986
|
const id = ++revealControllerSeq;
|
|
845
987
|
controllerIdRef.current = id;
|
|
846
988
|
revealRef.current = {
|
|
847
989
|
id,
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
}
|
|
990
|
+
surfaceOps,
|
|
991
|
+
// Restore-only "custom:" surfaces (e.g. afterAction) have no
|
|
992
|
+
// open state and must not keep the reveal "open" forever.
|
|
993
|
+
isStillOpen: () => {
|
|
994
|
+
const tracked = surfaceOps.filter((op) => !op.key.startsWith("custom:"));
|
|
995
|
+
return tracked.length === 0
|
|
996
|
+
? true
|
|
997
|
+
: tracked.some((op) => op.isOpen());
|
|
857
998
|
},
|
|
858
|
-
isStillOpen: () => openPredicates.length === 0
|
|
859
|
-
? true
|
|
860
|
-
: openPredicates.some((predicate) => predicate()),
|
|
861
999
|
};
|
|
862
1000
|
registerActiveReveal({
|
|
863
1001
|
id,
|