@parhelia/core 0.1.12884 → 0.1.12888

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 +13 -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 +189 -79
  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,9 +476,25 @@ 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();
@@ -487,6 +516,40 @@ function closeVersionSelectorIfOpen() {
487
516
  document.querySelector('[data-testid="version-selector"]');
488
517
  trigger?.click();
489
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
+ }
490
553
  function isAgentsPanelOpen() {
491
554
  const trigger = document.querySelector('[data-testid="agents-panel-button"]');
492
555
  return (trigger?.getAttribute("aria-pressed") === "true" ||
@@ -546,21 +609,32 @@ function SelectorButton({ selectorDef, keyProp, }) {
546
609
  revealRef.current = null;
547
610
  setIsActiveReveal(false);
548
611
  }, []);
549
- // Run the undo steps and reset. Used for explicit close (2nd click, auto
550
- // 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.
551
616
  const runRestore = useCallback(async () => {
552
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();
553
621
  // Reset button state immediately (button turns blue) while the surface
554
- // teardown animation plays out; the returned promise resolves once settled.
622
+ // teardown animation plays out; the promise resolves once settled.
555
623
  clearActiveReveal(reveal?.id ?? controllerIdRef.current);
556
624
  revealRef.current = null;
557
625
  setIsActiveReveal(false);
558
- 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;
559
633
  try {
560
- await reveal.restore();
634
+ await op.close();
561
635
  }
562
636
  catch {
563
- // ignore restore failures
637
+ // ignore individual close failures
564
638
  }
565
639
  }
566
640
  }, []);
@@ -647,7 +721,7 @@ function SelectorButton({ selectorDef, keyProp, }) {
647
721
  : isDisabled
648
722
  ? selectorDef.notFoundMessage || selectorDef.description
649
723
  : selectorDef.description;
650
- 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 () => {
651
725
  // Second click while active -> restore (close=click behaviour).
652
726
  if (isActiveReveal) {
653
727
  runRestore();
@@ -675,16 +749,17 @@ function SelectorButton({ selectorDef, keyProp, }) {
675
749
  return;
676
750
  }
677
751
  // State-machine semantics: any new Show-me action supersedes the
678
- // currently active State 3 reveal from another button, restoring
679
- // its UI first. Await the teardown so this reveal samples fresh
680
- // DOM and avoids a stale "was this surface open?" read. Because
681
- // clicks are serialized above, the superseded controller is
682
- // 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).
683
760
  const superseded = supersedeActiveReveal(controllerIdRef.current);
684
761
  if (superseded) {
685
762
  try {
686
- // Await the close so A's surface finishes its teardown
687
- // animation before B starts revealing (event-driven, not timed).
688
763
  await superseded.close();
689
764
  }
690
765
  catch {
@@ -711,28 +786,40 @@ function SelectorButton({ selectorDef, keyProp, }) {
711
786
  };
712
787
  await action(actionProps);
713
788
  };
714
- // Undo steps captured as we reveal, plus predicates telling
715
- // whether the revealed container is still open.
716
- const undoSteps = [];
717
- 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
+ };
718
801
  // 1. Sidebar-only: open the sidebar.
719
802
  if (isSidebarOnly &&
720
803
  editContext?.openSidebar &&
721
804
  selectorDef.location) {
722
805
  const sidebarId = selectorDef.location;
806
+ const surfaceKey = `sidebar:${sidebarId}`;
723
807
  const wasOpen = !!editContext.openSidebars?.includes(sidebarId);
724
808
  if (!wasOpen) {
725
809
  editContext.openSidebar(sidebarId);
726
- undoSteps.push(async () => {
727
- editContextRef.current?.toggleSidebar?.(sidebarId, {
728
- forceClose: true,
729
- });
730
- 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),
731
819
  });
732
820
  // Wait for the sidebar's open animation to finish before querying.
733
821
  await editContext.waitForSurfaceSettled?.(`sidebar:${sidebarId}`);
734
822
  }
735
- openPredicates.push(() => !!editContextRef.current?.openSidebars?.includes(sidebarId));
736
823
  }
737
824
  const availabilityElements = anchorSelector
738
825
  ? resolveElements(anchorSelector)
@@ -751,11 +838,14 @@ function SelectorButton({ selectorDef, keyProp, }) {
751
838
  if (shouldOpenEditorForm && editContext) {
752
839
  const slotId = editContext.getActiveSlotId();
753
840
  editContext.setEditorFormHiddenForSlot(slotId, false);
754
- undoSteps.push(async () => {
755
- editContextRef.current?.setEditorFormHiddenForSlot(slotId, true);
756
- 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()),
757
848
  });
758
- openPredicates.push(() => !isEditorFormSliderHidden(getEditorFormSlider()));
759
849
  await editContext.waitForSurfaceSettled?.("editor-form");
760
850
  }
761
851
  else if (selectorDef.tab) {
@@ -780,21 +870,32 @@ function SelectorButton({ selectorDef, keyProp, }) {
780
870
  if (selectorDef.beforeAction === "open-more-sidebars") {
781
871
  const morePanelsNowOpen = isElementPresent("@more-sidebars-panel");
782
872
  if (!morePanelsWasOpen && morePanelsNowOpen) {
783
- undoSteps.push(async () => {
784
- closeMorePanelsIfOpen();
785
- 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"),
786
881
  });
787
- openPredicates.push(() => isElementPresent("@more-sidebars-panel"));
788
882
  }
789
883
  }
790
884
  if (selectorDef.beforeAction === "open-version-selector") {
791
885
  const versionSelectorNowOpen = isElementPresent("@version-selector-list");
792
886
  if (!versionSelectorWasOpen && versionSelectorNowOpen) {
793
- undoSteps.push(async () => {
794
- closeVersionSelectorIfOpen();
795
- await waitForSelectorAnimations('[data-testid="version-selector-list"]');
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"),
796
898
  });
797
- openPredicates.push(() => isElementPresent("@version-selector-list"));
798
899
  }
799
900
  }
800
901
  if (selectorDef.beforeAction === "open-agents-panel" ||
@@ -803,46 +904,59 @@ function SelectorButton({ selectorDef, keyProp, }) {
803
904
  // animation (see config openAgentsPanel/openAgentSettings), so
804
905
  // no polling here.
805
906
  const agentsPanelNowOpen = isAgentsPanelOpen();
806
- // Push the panel-close undo first so that, since undo steps run
807
- // 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.
808
909
  if (!agentsPanelWasOpen && agentsPanelNowOpen) {
809
- undoSteps.push(async () => {
810
- if (editContextRef.current?.isMobile) {
811
- editContextRef.current?.toggleSidebar?.("agents-panel", {
812
- forceClose: true,
813
- });
814
- await editContextRef.current?.waitForSurfaceSettled?.("sidebar:agents-panel");
815
- }
816
- else {
817
- editContextRef.current?.setShowAgentsPanel?.(false);
818
- await editContextRef.current?.waitForSurfaceSettled?.("agents-panel");
819
- }
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(),
820
925
  });
821
- openPredicates.push(() => isAgentsPanelOpen());
822
926
  }
823
927
  if (selectorDef.beforeAction === "open-agent-settings") {
824
928
  const settingsTargetNowOpen = !!selectorDef.selector &&
825
929
  isElementPresent(selectorDef.selector);
826
930
  if (!agentSettingsWasOpen && settingsTargetNowOpen) {
827
- undoSteps.push(async () => {
828
- const trigger = document.querySelector('[data-testid="agent-settings-popover-trigger"]');
829
- if (trigger &&
830
- selectorDef.selector &&
831
- isElementPresent(selectorDef.selector)) {
832
- trigger.click();
833
- await waitForSelectorAnimations('[data-testid="agent-settings-popover-content"]');
834
- }
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,
835
945
  });
836
- openPredicates.push(() => selectorDef.selector
837
- ? isElementPresent(selectorDef.selector)
838
- : false);
839
946
  }
840
947
  }
841
948
  }
842
- // 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.
843
953
  if (selectorDef.afterAction) {
844
- undoSteps.push(async () => {
845
- 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,
846
960
  });
847
961
  }
848
962
  // Highlight the target. For sidebar-only selectors there is no
@@ -866,26 +980,22 @@ function SelectorButton({ selectorDef, keyProp, }) {
866
980
  highlightLifetime = highlightElement(highlightSelector, HIGHLIGHT_DURATION_MS);
867
981
  }
868
982
  }
869
- // 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
870
984
  // button active (green) and register it for mutual exclusion.
871
- if (undoSteps.length > 0) {
985
+ if (surfaceOps.length > 0) {
872
986
  const id = ++revealControllerSeq;
873
987
  controllerIdRef.current = id;
874
988
  revealRef.current = {
875
989
  id,
876
- restore: async () => {
877
- for (let i = undoSteps.length - 1; i >= 0; i -= 1) {
878
- try {
879
- await undoSteps[i]?.();
880
- }
881
- catch {
882
- // ignore individual undo failures
883
- }
884
- }
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());
885
998
  },
886
- isStillOpen: () => openPredicates.length === 0
887
- ? true
888
- : openPredicates.some((predicate) => predicate()),
889
999
  };
890
1000
  registerActiveReveal({
891
1001
  id,