@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.
Files changed (31) hide show
  1. package/dist/config/config.js +29 -1
  2. package/dist/config/config.js.map +1 -1
  3. package/dist/editor/LinkEditorDialog.js +25 -25
  4. package/dist/editor/LinkEditorDialog.js.map +1 -1
  5. package/dist/editor/client/EditorShell.js +1 -0
  6. package/dist/editor/client/EditorShell.js.map +1 -1
  7. package/dist/editor/client/operations.d.ts +2 -1
  8. package/dist/editor/client/operations.js +61 -5
  9. package/dist/editor/client/operations.js.map +1 -1
  10. package/dist/editor/field-types/richtext/components/ReactSlate.js +42 -32
  11. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  12. package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.js +25 -1
  13. package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.js.map +1 -1
  14. package/dist/editor/menubar/VersionSelector.js +16 -1
  15. package/dist/editor/menubar/VersionSelector.js.map +1 -1
  16. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +213 -75
  17. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
  18. package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.js +80 -32
  19. package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.js.map +1 -1
  20. package/dist/editor/page-viewer/PageViewerFrame.js +77 -0
  21. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  22. package/dist/editor/reviews/Comments.js +7 -0
  23. package/dist/editor/reviews/Comments.js.map +1 -1
  24. package/dist/editor/reviews/SuggestedEdit.js +9 -4
  25. package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
  26. package/dist/editor/reviews/suggestionContentChange.d.ts +2 -0
  27. package/dist/editor/reviews/suggestionContentChange.js +9 -0
  28. package/dist/editor/reviews/suggestionContentChange.js.map +1 -0
  29. package/dist/revision.d.ts +2 -2
  30. package/dist/revision.js +2 -2
  31. 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 (!isElementPresent("@more-sidebars-panel"))
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
- // Run the undo steps and reset. Used for explicit close (2nd click, auto
533
- // timer, or being superseded by another reveal via mutual exclusion).
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 returned promise resolves once settled.
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 reveal.restore();
634
+ await op.close();
544
635
  }
545
636
  catch {
546
- // ignore restore failures
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, restoring
662
- // its UI first. Await the teardown so this reveal samples fresh
663
- // DOM and avoids a stale "was this surface open?" read. Because
664
- // clicks are serialized above, the superseded controller is
665
- // always the previous fully-settled reveal (no race here).
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
- // Undo steps captured as we reveal, plus predicates telling
698
- // whether the revealed container is still open.
699
- const undoSteps = [];
700
- const openPredicates = [];
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
- undoSteps.push(async () => {
710
- editContextRef.current?.toggleSidebar?.(sidebarId, {
711
- forceClose: true,
712
- });
713
- await editContextRef.current?.waitForSurfaceSettled?.(`sidebar:${sidebarId}`);
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
- undoSteps.push(async () => {
738
- editContextRef.current?.setEditorFormHiddenForSlot(slotId, true);
739
- await editContextRef.current?.waitForSurfaceSettled?.("editor-form");
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
- undoSteps.push(async () => {
766
- closeMorePanelsIfOpen();
767
- await waitForSelectorAnimations('[data-testid="more-sidebars-panel"]');
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-close undo first so that, since undo steps run
779
- // in reverse, the settings popover is closed before the panel.
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
- undoSteps.push(async () => {
782
- if (editContextRef.current?.isMobile) {
783
- editContextRef.current?.toggleSidebar?.("agents-panel", {
784
- forceClose: true,
785
- });
786
- await editContextRef.current?.waitForSurfaceSettled?.("sidebar:agents-panel");
787
- }
788
- else {
789
- editContextRef.current?.setShowAgentsPanel?.(false);
790
- await editContextRef.current?.waitForSurfaceSettled?.("agents-panel");
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
- undoSteps.push(async () => {
800
- const trigger = document.querySelector('[data-testid="agent-settings-popover-trigger"]');
801
- if (trigger &&
802
- selectorDef.selector &&
803
- isElementPresent(selectorDef.selector)) {
804
- trigger.click();
805
- await waitForSelectorAnimations('[data-testid="agent-settings-popover-content"]');
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. field actions).
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
- undoSteps.push(async () => {
817
- await runManualAction(selectorDef.afterAction, !isSidebarOnly ? resolveElements() : availabilityElements);
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/undid anything, this was a State-3 reveal: keep the
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 (undoSteps.length > 0) {
985
+ if (surfaceOps.length > 0) {
844
986
  const id = ++revealControllerSeq;
845
987
  controllerIdRef.current = id;
846
988
  revealRef.current = {
847
989
  id,
848
- restore: async () => {
849
- for (let i = undoSteps.length - 1; i >= 0; i -= 1) {
850
- try {
851
- await undoSteps[i]?.();
852
- }
853
- catch {
854
- // ignore individual undo failures
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,