@parhelia/core 0.1.12881 → 0.1.12883
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/agents-view/AgentsSidebar.js +1 -1
- package/dist/agents-view/AgentsSidebar.js.map +1 -1
- package/dist/agents-view/AgentsTitlebar.d.ts +1 -1
- package/dist/agents-view/AgentsTitlebar.js +3 -6
- package/dist/agents-view/AgentsTitlebar.js.map +1 -1
- package/dist/agents-view/AgentsView.d.ts +2 -2
- package/dist/agents-view/AgentsView.js +2 -2
- package/dist/agents-view/AgentsView.js.map +1 -1
- package/dist/agents-view/AgentsWorkspaceView.js +1 -12
- package/dist/agents-view/AgentsWorkspaceView.js.map +1 -1
- package/dist/agents-view/CreateAgentView.d.ts +1 -1
- package/dist/agents-view/CreateAgentView.js +1 -1
- package/dist/agents-view/DateAgentsGroup.js +12 -1
- package/dist/agents-view/DateAgentsGroup.js.map +1 -1
- package/dist/agents-view/ProfileAgentsGroup.js +16 -4
- package/dist/agents-view/ProfileAgentsGroup.js.map +1 -1
- package/dist/components/ui/checkbox.js +1 -1
- package/dist/components/ui/checkbox.js.map +1 -1
- package/dist/components/ui/context-menu.d.ts +2 -1
- package/dist/components/ui/context-menu.js +4 -1
- package/dist/components/ui/context-menu.js.map +1 -1
- package/dist/components/ui/input.js +2 -2
- package/dist/components/ui/input.js.map +1 -1
- package/dist/components/ui/select.js +1 -1
- package/dist/components/ui/select.js.map +1 -1
- package/dist/components/ui/textarea.js +2 -2
- package/dist/components/ui/textarea.js.map +1 -1
- package/dist/config/config.js +92 -14
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +7 -0
- package/dist/config/types.js.map +1 -1
- package/dist/editor/ContextMenu.d.ts +1 -0
- package/dist/editor/ContextMenu.js +4 -4
- package/dist/editor/ContextMenu.js.map +1 -1
- package/dist/editor/FieldHistory.d.ts +2 -1
- package/dist/editor/FieldHistory.js +13 -12
- package/dist/editor/FieldHistory.js.map +1 -1
- package/dist/editor/FieldListField.js +4 -18
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/LinkEditorDialog.d.ts +9 -2
- package/dist/editor/LinkEditorDialog.js +174 -70
- package/dist/editor/LinkEditorDialog.js.map +1 -1
- package/dist/editor/MainLayout.js +49 -6
- package/dist/editor/MainLayout.js.map +1 -1
- package/dist/editor/MobileLayout.js +33 -1
- package/dist/editor/MobileLayout.js.map +1 -1
- package/dist/editor/PictureCropper.js +45 -28
- package/dist/editor/PictureCropper.js.map +1 -1
- package/dist/editor/ai/AgentProfileSelector.js +7 -7
- package/dist/editor/ai/AgentProfileSelector.js.map +1 -1
- package/dist/editor/ai/Agents.js +19 -5
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/InlineAiDialog.d.ts +1 -0
- package/dist/editor/ai/InlineAiDialog.js +239 -198
- package/dist/editor/ai/InlineAiDialog.js.map +1 -1
- package/dist/editor/ai/InlineAiTextEditTooltip.d.ts +8 -0
- package/dist/editor/ai/InlineAiTextEditTooltip.js +10 -0
- package/dist/editor/ai/InlineAiTextEditTooltip.js.map +1 -0
- package/dist/editor/ai/InlineAiTrigger.js +187 -20
- package/dist/editor/ai/InlineAiTrigger.js.map +1 -1
- package/dist/editor/ai/inlineAiTextEditLabels.d.ts +2 -0
- package/dist/editor/ai/inlineAiTextEditLabels.js +8 -0
- package/dist/editor/ai/inlineAiTextEditLabels.js.map +1 -0
- package/dist/editor/ai/prepareInlineAiTextSelection.d.ts +5 -0
- package/dist/editor/ai/prepareInlineAiTextSelection.js +86 -0
- package/dist/editor/ai/prepareInlineAiTextSelection.js.map +1 -0
- package/dist/editor/ai/terminal/components/AgentSettingsPopover.js +1 -1
- package/dist/editor/ai/terminal/components/AgentSettingsPopover.js.map +1 -1
- package/dist/editor/ai/terminal/components/AiResponseMessage.js +9 -9
- package/dist/editor/ai/terminal/components/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/terminal/components/ToolCallDisplay.js +3 -1
- package/dist/editor/ai/terminal/components/ToolCallDisplay.js.map +1 -1
- package/dist/editor/ai/useActiveAgentConversation.d.ts +3 -0
- package/dist/editor/ai/useActiveAgentConversation.js +32 -0
- package/dist/editor/ai/useActiveAgentConversation.js.map +1 -0
- package/dist/editor/ai/useInlineAiPosition.d.ts +9 -1
- package/dist/editor/ai/useInlineAiPosition.js +10 -19
- package/dist/editor/ai/useInlineAiPosition.js.map +1 -1
- package/dist/editor/client/EditorShell.js +23 -4
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +25 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/fieldModificationStore.d.ts +1 -0
- package/dist/editor/client/fieldModificationStore.js +7 -2
- package/dist/editor/client/fieldModificationStore.js.map +1 -1
- package/dist/editor/client/itemsRepository.js +3 -1
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/operations.js +26 -4
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/client/ui/EditorChrome.js +1 -1
- package/dist/editor/client/ui/EditorChrome.js.map +1 -1
- package/dist/editor/commands/componentCommands.d.ts +3 -1
- package/dist/editor/commands/componentCommands.js +8 -3
- package/dist/editor/commands/componentCommands.js.map +1 -1
- package/dist/editor/commands/handlers/uiActionHandlers.js +5 -1
- package/dist/editor/commands/handlers/uiActionHandlers.js.map +1 -1
- package/dist/editor/editor-warnings/FinalWorkflowStateReadOnly.js +5 -0
- package/dist/editor/editor-warnings/FinalWorkflowStateReadOnly.js.map +1 -1
- package/dist/editor/editor-warnings/ItemLocked.js +6 -3
- package/dist/editor/editor-warnings/ItemLocked.js.map +1 -1
- package/dist/editor/field-types/DateFieldEditor.js +1 -1
- package/dist/editor/field-types/DateFieldEditor.js.map +1 -1
- package/dist/editor/field-types/DateTimeFieldEditor.js +1 -1
- package/dist/editor/field-types/DateTimeFieldEditor.js.map +1 -1
- package/dist/editor/field-types/DropLinkEditor.js +1 -1
- package/dist/editor/field-types/DropLinkEditor.js.map +1 -1
- package/dist/editor/field-types/DropListEditor.js +1 -1
- package/dist/editor/field-types/DropListEditor.js.map +1 -1
- package/dist/editor/field-types/ImageFieldEditor.js +1 -1
- package/dist/editor/field-types/ImageFieldEditor.js.map +1 -1
- package/dist/editor/field-types/InternalLinkFieldEditor.js +1 -1
- package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
- package/dist/editor/field-types/LinkFieldEditor.js +15 -3
- package/dist/editor/field-types/LinkFieldEditor.js.map +1 -1
- package/dist/editor/field-types/MultiLineText.js +1 -1
- package/dist/editor/field-types/MultiLineText.js.map +1 -1
- package/dist/editor/field-types/NameValueListEditor.js +1 -1
- package/dist/editor/field-types/NameValueListEditor.js.map +1 -1
- package/dist/editor/field-types/PictureFieldEditor.js +2 -2
- package/dist/editor/field-types/PictureFieldEditor.js.map +1 -1
- package/dist/editor/field-types/RawEditor.js +1 -1
- package/dist/editor/field-types/RawEditor.js.map +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.js +18 -36
- package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
- package/dist/editor/field-types/SingleLineText.js +1 -1
- package/dist/editor/field-types/SingleLineText.js.map +1 -1
- package/dist/editor/field-types/TreeListEditor.js +1 -1
- package/dist/editor/field-types/TreeListEditor.js.map +1 -1
- package/dist/editor/field-types/richtext/bridgeRichTextProfile.d.ts +21 -0
- package/dist/editor/field-types/richtext/bridgeRichTextProfile.js +96 -0
- package/dist/editor/field-types/richtext/bridgeRichTextProfile.js.map +1 -0
- package/dist/editor/field-types/richtext/components/ReactSlate.css +44 -6
- package/dist/editor/field-types/richtext/components/ReactSlate.js +185 -36
- package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
- package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.css +5 -2
- package/dist/editor/field-types/richtext/components/SimpleToolbar.js +5 -4
- package/dist/editor/field-types/richtext/components/SimpleToolbar.js.map +1 -1
- package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +2 -14
- package/dist/editor/field-types/richtext/contextMenuFactory.js +4 -232
- package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
- package/dist/editor/field-types/richtext/richTextToolbarIcons.d.ts +7 -0
- package/dist/editor/field-types/richtext/richTextToolbarIcons.js +49 -0
- package/dist/editor/field-types/richtext/richTextToolbarIcons.js.map +1 -0
- package/dist/editor/field-types/richtext/utils/conversion.js +23 -2
- package/dist/editor/field-types/richtext/utils/conversion.js.map +1 -1
- package/dist/editor/fieldTypes.d.ts +2 -0
- package/dist/editor/media-selector/MediaFolderBrowser.d.ts +2 -1
- package/dist/editor/media-selector/MediaFolderBrowser.js +19 -9
- package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
- package/dist/editor/media-selector/TreeSelector.js +15 -9
- package/dist/editor/media-selector/TreeSelector.js.map +1 -1
- package/dist/editor/media-selector/UploadZone.d.ts +2 -1
- package/dist/editor/media-selector/UploadZone.js +21 -9
- package/dist/editor/media-selector/UploadZone.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.d.ts +10 -0
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +607 -85
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js +3 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.d.ts +3 -1
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.js +357 -73
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.js.map +1 -1
- package/dist/editor/page-editor-chrome/PageEditorChrome.js +1 -1
- package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
- package/dist/editor/page-editor-chrome/bridgeInlineFormatToolbarLayout.d.ts +24 -0
- package/dist/editor/page-editor-chrome/bridgeInlineFormatToolbarLayout.js +89 -0
- package/dist/editor/page-editor-chrome/bridgeInlineFormatToolbarLayout.js.map +1 -0
- package/dist/editor/page-editor-chrome/useBridgeInlineEditing.d.ts +1 -1
- package/dist/editor/page-editor-chrome/useBridgeInlineEditing.js +7 -1
- package/dist/editor/page-editor-chrome/useBridgeInlineEditing.js.map +1 -1
- package/dist/editor/page-viewer/PageViewer.js +23 -4
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +157 -2
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/reviews/Comment.js +2 -2
- package/dist/editor/reviews/Comment.js.map +1 -1
- package/dist/editor/reviews/CommentDisplayPopover.js +2 -1
- package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/CommentEditor.d.ts +1 -0
- package/dist/editor/reviews/CommentEditor.js +3 -2
- package/dist/editor/reviews/CommentEditor.js.map +1 -1
- package/dist/editor/reviews/CommentPopover.js +2 -2
- package/dist/editor/reviews/CommentPopover.js.map +1 -1
- package/dist/editor/reviews/Comments.js +5 -4
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/FeedbackCard.js +5 -7
- package/dist/editor/reviews/FeedbackCard.js.map +1 -1
- package/dist/editor/reviews/SuggestionCommentThread.js +3 -3
- package/dist/editor/reviews/SuggestionCommentThread.js.map +1 -1
- package/dist/editor/services/contentService.d.ts +1 -0
- package/dist/editor/services/contentService.js.map +1 -1
- package/dist/editor/sidebar/Validation.js +4 -1
- package/dist/editor/sidebar/Validation.js.map +1 -1
- package/dist/editor/template-wizard/TemplateStructureInlineEditor.js +1 -1
- package/dist/editor/template-wizard/TemplateStructureInlineEditor.js.map +1 -1
- package/dist/editor/ui/SimpleIconButton.js +8 -2
- package/dist/editor/ui/SimpleIconButton.js.map +1 -1
- package/dist/editor/ui/Splitter.d.ts +1 -0
- package/dist/editor/ui/Splitter.js +12 -2
- package/dist/editor/ui/Splitter.js.map +1 -1
- package/dist/editor/ui/animationSettle.d.ts +32 -0
- package/dist/editor/ui/animationSettle.js +85 -0
- package/dist/editor/ui/animationSettle.js.map +1 -0
- package/dist/editor/utils/expandSelectionAtCaret.d.ts +15 -0
- package/dist/editor/utils/expandSelectionAtCaret.js +183 -0
- package/dist/editor/utils/expandSelectionAtCaret.js.map +1 -0
- package/dist/editor/views/MediaFolderEditView.js +1 -1
- package/dist/editor/views/MediaFolderEditView.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/DialogWrappers.js +2 -2
- package/dist/splash-screen/DialogWrappers.js.map +1 -1
- package/dist/task-board/components/TaskDetailPanel.js +2 -1
- package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
- package/dist/task-board/views/DependencyGraphView.d.ts +42 -1
- package/dist/task-board/views/DependencyGraphView.js +94 -0
- package/dist/task-board/views/DependencyGraphView.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
- package/styles.css +59 -12
|
@@ -11,12 +11,58 @@ import { useEditContext } from "../../client/editContext";
|
|
|
11
11
|
import { cn } from "../../../lib/utils";
|
|
12
12
|
import { sanitizeSvg } from "../../../lib/sanitize";
|
|
13
13
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "../../../components/ui/dropdown-menu";
|
|
14
|
+
import { waitForSelectorAnimations } from "../../ui/animationSettle";
|
|
14
15
|
/** Render a sanitized SVG string at a specific pixel size. */
|
|
15
16
|
function SvgIcon({ svg, size, className, }) {
|
|
16
17
|
const sanitizedSvg = sanitizeSvg(svg).trim();
|
|
17
18
|
return (_jsx("span", { className: cn("inline-flex items-center justify-center [&>svg]:block [&>svg]:h-full [&>svg]:w-full", className), style: { width: size, height: size }, dangerouslySetInnerHTML: { __html: sanitizedSvg } }));
|
|
18
19
|
}
|
|
19
20
|
const HIGHLIGHT_DURATION_MS = 2000;
|
|
21
|
+
const EDITOR_FORM_SLIDER_SELECTOR = '[data-testid="editor-form-slider"]';
|
|
22
|
+
const EDITOR_FORM_HIDDEN_ATTRIBUTE = "aria-hidden";
|
|
23
|
+
const CLOSE_PANE_LABEL = "Close pane";
|
|
24
|
+
function waitForDelay(delayMs) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
window.setTimeout(resolve, delayMs);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function getEditorFormSlider(elements = []) {
|
|
30
|
+
for (const element of elements) {
|
|
31
|
+
const slider = element.closest(EDITOR_FORM_SLIDER_SELECTOR);
|
|
32
|
+
if (slider) {
|
|
33
|
+
return slider;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return document.querySelector(EDITOR_FORM_SLIDER_SELECTOR);
|
|
37
|
+
}
|
|
38
|
+
function isEditorFormSliderHidden(slider) {
|
|
39
|
+
return slider?.getAttribute(EDITOR_FORM_HIDDEN_ATTRIBUTE) === "true";
|
|
40
|
+
}
|
|
41
|
+
async function waitForResolvedElements(resolveElements, selector, timeoutMs = 1000) {
|
|
42
|
+
const startedAt = Date.now();
|
|
43
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
44
|
+
const elements = resolveElements(selector);
|
|
45
|
+
if (elements.length > 0) {
|
|
46
|
+
return elements;
|
|
47
|
+
}
|
|
48
|
+
await waitForDelay(50);
|
|
49
|
+
}
|
|
50
|
+
return resolveElements(selector);
|
|
51
|
+
}
|
|
52
|
+
// Wait until at least one element for the selector is actually VISIBLE (not just
|
|
53
|
+
// present). After a reveal/animation a target can be in the DOM but not yet laid
|
|
54
|
+
// out, which would otherwise cause highlightElement to skip it.
|
|
55
|
+
async function waitForVisibleElements(selector, timeoutMs = 1000) {
|
|
56
|
+
const startedAt = Date.now();
|
|
57
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
58
|
+
const elements = getVisibleElementsForSelector(selector);
|
|
59
|
+
if (elements.length > 0) {
|
|
60
|
+
return elements;
|
|
61
|
+
}
|
|
62
|
+
await waitForDelay(50);
|
|
63
|
+
}
|
|
64
|
+
return getVisibleElementsForSelector(selector);
|
|
65
|
+
}
|
|
20
66
|
const manualMarkdownComponents = {
|
|
21
67
|
h1: ({ children }) => (_jsx("h1", { className: "text-neutral-grey-100 mt-4 mb-0 text-xl font-bold", children: children })),
|
|
22
68
|
h2: ({ children }) => (_jsx("h2", { className: "text-neutral-grey-100 mt-3 mb-0 text-lg font-semibold", children: children })),
|
|
@@ -52,7 +98,15 @@ export function parseUiSelectors(uiSelectors) {
|
|
|
52
98
|
let beforeAction;
|
|
53
99
|
let afterAction;
|
|
54
100
|
let availabilitySelector;
|
|
101
|
+
let selectorOverride;
|
|
102
|
+
let tab;
|
|
103
|
+
let closeMode;
|
|
55
104
|
for (const segment of segments) {
|
|
105
|
+
if (segment.startsWith("close=")) {
|
|
106
|
+
const value = segment.substring("close=".length).trim();
|
|
107
|
+
closeMode = value === "auto" ? "auto" : "click";
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
56
110
|
if (segment.startsWith("beforeAction=")) {
|
|
57
111
|
beforeAction = segment.substring("beforeAction=".length).trim();
|
|
58
112
|
continue;
|
|
@@ -67,6 +121,14 @@ export function parseUiSelectors(uiSelectors) {
|
|
|
67
121
|
.trim();
|
|
68
122
|
continue;
|
|
69
123
|
}
|
|
124
|
+
if (segment.startsWith("selector=")) {
|
|
125
|
+
selectorOverride = segment.substring("selector=".length).trim();
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (segment.startsWith("tab=")) {
|
|
129
|
+
tab = segment.substring("tab=".length).trim();
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
70
132
|
if (!notFoundMessage) {
|
|
71
133
|
notFoundMessage = segment;
|
|
72
134
|
}
|
|
@@ -80,14 +142,16 @@ export function parseUiSelectors(uiSelectors) {
|
|
|
80
142
|
: undefined;
|
|
81
143
|
const parsed = {
|
|
82
144
|
name,
|
|
83
|
-
selector: isSidebarOnly ? "" : `@${name}`,
|
|
145
|
+
selector: isSidebarOnly ? "" : selectorOverride || `@${name}`,
|
|
84
146
|
availabilitySelector,
|
|
147
|
+
tab,
|
|
85
148
|
description,
|
|
86
149
|
notFoundMessage: notFoundMessage || undefined,
|
|
87
150
|
location: isSidebarOnly ? sidebarId : undefined, // Use actual sidebar ID (without -sidebar suffix) for sidebar-only
|
|
88
151
|
isSidebarOnly,
|
|
89
152
|
beforeAction,
|
|
90
153
|
afterAction,
|
|
154
|
+
closeMode,
|
|
91
155
|
};
|
|
92
156
|
// Primary key remains the selector name
|
|
93
157
|
map.set(name, parsed);
|
|
@@ -148,6 +212,46 @@ function getElementsForSelector(selector) {
|
|
|
148
212
|
}
|
|
149
213
|
return Array.from(document.querySelectorAll(cssSelector));
|
|
150
214
|
}
|
|
215
|
+
// Determine whether an element is genuinely visible to the user, not merely
|
|
216
|
+
// present in the DOM. A target sitting inside a collapsed panel, a hidden
|
|
217
|
+
// sidebar (aria-hidden), or with `display:none` / `visibility:hidden` / zero
|
|
218
|
+
// size counts as NOT visible.
|
|
219
|
+
function isElementVisible(element) {
|
|
220
|
+
// Treat anything inside an explicitly aria-hidden container (e.g. a collapsed
|
|
221
|
+
// editor form slider) as not visible.
|
|
222
|
+
if (element.closest('[aria-hidden="true"]')) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
const candidate = element;
|
|
226
|
+
if (typeof candidate.checkVisibility === "function") {
|
|
227
|
+
if (!candidate.checkVisibility({
|
|
228
|
+
checkOpacity: true,
|
|
229
|
+
checkVisibilityCSS: true,
|
|
230
|
+
})) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
// Fallback for environments without Element.checkVisibility (e.g. jsdom).
|
|
236
|
+
const style = window.getComputedStyle(element);
|
|
237
|
+
if (style.display === "none" ||
|
|
238
|
+
style.visibility === "hidden" ||
|
|
239
|
+
style.visibility === "collapse") {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
// position:fixed elements have a null offsetParent but can still be visible.
|
|
243
|
+
if (element.offsetParent === null && style.position !== "fixed") {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Reject zero-area elements (e.g. a width:0 collapsed panel).
|
|
248
|
+
const rect = element.getBoundingClientRect();
|
|
249
|
+
return rect.width > 0 && rect.height > 0;
|
|
250
|
+
}
|
|
251
|
+
// Return only the elements matching a selector that are currently visible.
|
|
252
|
+
function getVisibleElementsForSelector(selector) {
|
|
253
|
+
return getElementsForSelector(selector).filter(isElementVisible);
|
|
254
|
+
}
|
|
151
255
|
function shouldSkipHighlightScroll(element) {
|
|
152
256
|
const rect = element.getBoundingClientRect();
|
|
153
257
|
const computedStyle = window.getComputedStyle(element);
|
|
@@ -199,22 +303,60 @@ async function scrollElementForHighlight(element) {
|
|
|
199
303
|
// Highlight an element temporarily
|
|
200
304
|
export async function highlightElement(selector, duration = HIGHLIGHT_DURATION_MS) {
|
|
201
305
|
const cssSelector = expandSelector(selector);
|
|
202
|
-
|
|
306
|
+
// Only ever highlight elements that are actually visible. A target inside a
|
|
307
|
+
// collapsed panel or hidden sidebar must not receive a stray overlay.
|
|
308
|
+
const elements = getElementsForSelector(selector).filter(isElementVisible);
|
|
203
309
|
if (elements.length === 0)
|
|
204
310
|
return;
|
|
311
|
+
const overlayLifetimes = [];
|
|
312
|
+
if (cssSelector.startsWith("iframe:")) {
|
|
313
|
+
const iframe = document.querySelector("iframe.page-iframe");
|
|
314
|
+
const iframeRect = iframe?.getBoundingClientRect();
|
|
315
|
+
if (elements[0]) {
|
|
316
|
+
await scrollElementForHighlight(elements[0]);
|
|
317
|
+
}
|
|
318
|
+
elements.forEach((element) => {
|
|
319
|
+
const elementRect = element.getBoundingClientRect();
|
|
320
|
+
overlayLifetimes.push(showHighlightOverlay(elementRect.left + (iframeRect?.left ?? 0), elementRect.top + (iframeRect?.top ?? 0), elementRect.width, elementRect.height, duration));
|
|
321
|
+
});
|
|
322
|
+
await Promise.all(overlayLifetimes);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
205
325
|
if (elements[0]) {
|
|
206
326
|
await scrollElementForHighlight(elements[0]);
|
|
207
327
|
}
|
|
208
328
|
elements.forEach((element) => {
|
|
209
329
|
const rect = element.getBoundingClientRect();
|
|
210
|
-
showHighlightOverlay(rect.left, rect.top, rect.width, rect.height, duration);
|
|
330
|
+
overlayLifetimes.push(showHighlightOverlay(rect.left, rect.top, rect.width, rect.height, duration));
|
|
211
331
|
});
|
|
332
|
+
await Promise.all(overlayLifetimes);
|
|
212
333
|
}
|
|
213
|
-
// Check if an element is currently
|
|
214
|
-
function
|
|
334
|
+
// Check if an element is currently present in the DOM (regardless of visibility)
|
|
335
|
+
function isElementPresent(selector) {
|
|
215
336
|
return getElementsForSelector(selector).length > 0;
|
|
216
337
|
}
|
|
217
|
-
//
|
|
338
|
+
// Check if an element matching the selector is currently visible to the user
|
|
339
|
+
function isElementVisibleBySelector(selector) {
|
|
340
|
+
return getVisibleElementsForSelector(selector).length > 0;
|
|
341
|
+
}
|
|
342
|
+
export function deriveSelectorState(params) {
|
|
343
|
+
if (!params.isAnchorReachable) {
|
|
344
|
+
return "disabled";
|
|
345
|
+
}
|
|
346
|
+
// Sidebar-only selectors have no element to "see"; opening the sidebar is the action.
|
|
347
|
+
if (params.isSidebarOnly) {
|
|
348
|
+
return "show-action";
|
|
349
|
+
}
|
|
350
|
+
if (params.isTargetVisible) {
|
|
351
|
+
return "show";
|
|
352
|
+
}
|
|
353
|
+
if (params.hasRevealPath) {
|
|
354
|
+
return "show-action";
|
|
355
|
+
}
|
|
356
|
+
return "disabled";
|
|
357
|
+
}
|
|
358
|
+
// Create a temporary highlight overlay. Resolves when its finite "life"
|
|
359
|
+
// animation ends and the overlay has been removed (event-driven, not a timer).
|
|
218
360
|
function showHighlightOverlay(x, y, width, height, duration) {
|
|
219
361
|
const padding = 4;
|
|
220
362
|
const viewportWidth = window.innerWidth;
|
|
@@ -231,9 +373,11 @@ function showHighlightOverlay(x, y, width, height, duration) {
|
|
|
231
373
|
const clampedHeight = bottom - top;
|
|
232
374
|
// Skip if target is fully outside the viewport after clamping.
|
|
233
375
|
if (clampedWidth <= 0 || clampedHeight <= 0)
|
|
234
|
-
return;
|
|
376
|
+
return Promise.resolve();
|
|
235
377
|
const overlay = document.createElement("div");
|
|
236
378
|
overlay.className = "selector-highlight-overlay";
|
|
379
|
+
// Two animations: the infinite pulse for the visual, and a finite "life"
|
|
380
|
+
// animation whose animationend drives removal (single source of duration).
|
|
237
381
|
overlay.style.cssText = `
|
|
238
382
|
position: fixed;
|
|
239
383
|
left: ${left}px;
|
|
@@ -245,7 +389,7 @@ function showHighlightOverlay(x, y, width, height, duration) {
|
|
|
245
389
|
background: color-mix(in srgb, var(--color-component-default) 10%, transparent);
|
|
246
390
|
pointer-events: none;
|
|
247
391
|
z-index: 999999;
|
|
248
|
-
animation: pulse-highlight 1s ease-in-out infinite;
|
|
392
|
+
animation: pulse-highlight 1s ease-in-out infinite, selector-highlight-life ${duration}ms linear forwards;
|
|
249
393
|
box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-component-default) 20%, transparent);
|
|
250
394
|
`;
|
|
251
395
|
// Add animation keyframes if not already added
|
|
@@ -257,114 +401,492 @@ function showHighlightOverlay(x, y, width, height, duration) {
|
|
|
257
401
|
0%, 100% { box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-component-default) 20%, transparent); }
|
|
258
402
|
50% { box-shadow: 0 0 0 8px color-mix(in srgb, var(--color-component-default) 10%, transparent); }
|
|
259
403
|
}
|
|
404
|
+
@keyframes selector-highlight-life {
|
|
405
|
+
from { opacity: 1; }
|
|
406
|
+
to { opacity: 1; }
|
|
407
|
+
}
|
|
260
408
|
`;
|
|
261
409
|
document.head.appendChild(style);
|
|
262
410
|
}
|
|
263
411
|
document.body.appendChild(overlay);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
412
|
+
return new Promise((resolve) => {
|
|
413
|
+
let finished = false;
|
|
414
|
+
const finish = () => {
|
|
415
|
+
if (finished)
|
|
416
|
+
return;
|
|
417
|
+
finished = true;
|
|
418
|
+
overlay.removeEventListener("animationend", onAnimationEnd);
|
|
419
|
+
overlay.remove();
|
|
420
|
+
resolve();
|
|
421
|
+
};
|
|
422
|
+
const onAnimationEnd = (event) => {
|
|
423
|
+
// Ignore the infinite pulse; only the finite life animation ends.
|
|
424
|
+
if (event.animationName === "selector-highlight-life") {
|
|
425
|
+
finish();
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
overlay.addEventListener("animationend", onAnimationEnd);
|
|
429
|
+
// Guardrail: if animationend is ever missed, remove shortly after duration.
|
|
430
|
+
window.setTimeout(finish, duration + 500);
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
let activeRevealController = null;
|
|
434
|
+
let revealControllerSeq = 0;
|
|
435
|
+
// Serialize Show-me reveal clicks across ALL buttons so overlapping rapid clicks
|
|
436
|
+
// can never interleave. Each click chains behind the previous handler and grabs
|
|
437
|
+
// a monotonic token. This closes the race where, during the
|
|
438
|
+
// `await superseded.close()` teardown window, no global controller is registered:
|
|
439
|
+
// without serialization a faster later click could start, become active, and
|
|
440
|
+
// then be overwritten by this (older, still-awaiting) handler — i.e. NOT
|
|
441
|
+
// "last click wins". With the chain, a newer click waits for this one to fully
|
|
442
|
+
// settle (and is then properly superseded), and an older handler that has been
|
|
443
|
+
// overtaken while waiting discards its work via the token check.
|
|
444
|
+
let revealClickChain = Promise.resolve();
|
|
445
|
+
let revealClickSeq = 0;
|
|
446
|
+
// Store the new active reveal. Superseding the previous one is done explicitly
|
|
447
|
+
// at click start (see supersedeActiveReveal), so this only records state.
|
|
448
|
+
function registerActiveReveal(controller) {
|
|
449
|
+
activeRevealController = controller;
|
|
450
|
+
}
|
|
451
|
+
// Detach and return the currently active reveal unless it belongs to exceptId.
|
|
452
|
+
// The caller is responsible for closing it (and awaiting its teardown).
|
|
453
|
+
function supersedeActiveReveal(exceptId) {
|
|
454
|
+
const current = activeRevealController;
|
|
455
|
+
if (current && current.id !== exceptId) {
|
|
456
|
+
activeRevealController = null;
|
|
457
|
+
return current;
|
|
458
|
+
}
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
function clearActiveReveal(id) {
|
|
462
|
+
if (activeRevealController?.id === id) {
|
|
463
|
+
activeRevealController = null;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// Close the More-panels flyout if it is currently open (inverse of open-more-sidebars).
|
|
467
|
+
function closeMorePanelsIfOpen() {
|
|
468
|
+
if (!isElementPresent("@more-sidebars-panel"))
|
|
469
|
+
return;
|
|
470
|
+
const trigger = document.querySelector('[data-testid="more-sidebars-button"]');
|
|
471
|
+
trigger?.click();
|
|
472
|
+
}
|
|
473
|
+
function isAgentsPanelOpen() {
|
|
474
|
+
const trigger = document.querySelector('[data-testid="agents-panel-button"]');
|
|
475
|
+
return (trigger?.getAttribute("aria-pressed") === "true" ||
|
|
476
|
+
isElementVisibleBySelector("@sidebar-panel-agents-panel"));
|
|
267
477
|
}
|
|
268
478
|
// Button component for "Show me" functionality with availability detection
|
|
269
479
|
function SelectorButton({ selectorDef, keyProp, }) {
|
|
270
480
|
const editContext = useEditContext();
|
|
271
481
|
const manualActions = editContext?.configuration.editor.manualActions;
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
//
|
|
279
|
-
const
|
|
482
|
+
const isSidebarOnly = !!selectorDef.isSidebarOnly;
|
|
483
|
+
const targetSelector = selectorDef.selector;
|
|
484
|
+
// Anchor = explicit availabilitySelector only. It gates the disabled state.
|
|
485
|
+
const anchorSelector = selectorDef.availabilitySelector;
|
|
486
|
+
const hasAvailabilitySelector = Boolean(anchorSelector);
|
|
487
|
+
const closeMode = selectorDef.closeMode ?? "click";
|
|
488
|
+
// Keep a fresh reference to editContext for use inside reveal/restore closures.
|
|
489
|
+
const editContextRef = useRef(editContext);
|
|
490
|
+
editContextRef.current = editContext;
|
|
280
491
|
const hasBeforeAction = Boolean(selectorDef.beforeAction &&
|
|
281
492
|
manualActions?.[selectorDef.beforeAction] &&
|
|
282
493
|
editContext);
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
494
|
+
// A sidebar-only selector is reachable only when the current workspace
|
|
495
|
+
// supports sidebars and offers this specific sidebar (both are workspace-scoped).
|
|
496
|
+
const canAutoOpenSidebar = Boolean(isSidebarOnly &&
|
|
497
|
+
selectorDef.location &&
|
|
498
|
+
editContext?.openSidebar &&
|
|
499
|
+
editContext.workspace?.supportsSidebars &&
|
|
500
|
+
editContext.availableSidebars?.some((s) => s.id === selectorDef.location));
|
|
501
|
+
// Presence/visibility probes, kept up to date by the effect below.
|
|
502
|
+
// The anchor gates "is this relevant in the current context", so it checks
|
|
503
|
+
// presence (it may itself be a currently-collapsed container). The target
|
|
504
|
+
// checks visibility to distinguish "show" from "show-action".
|
|
505
|
+
const [isTargetPresent, setIsTargetPresent] = useState(false);
|
|
506
|
+
const [isTargetVisible, setIsTargetVisible] = useState(false);
|
|
507
|
+
const [isAnchorPresent, setIsAnchorPresent] = useState(false);
|
|
508
|
+
// True while THIS button has revealed a container (State 3 active -> green).
|
|
509
|
+
const [isActiveReveal, setIsActiveReveal] = useState(false);
|
|
510
|
+
const controllerIdRef = useRef(0);
|
|
511
|
+
const revealRef = useRef(null);
|
|
512
|
+
const isAnchorReachable = hasAvailabilitySelector
|
|
513
|
+
? isAnchorPresent
|
|
514
|
+
: isSidebarOnly
|
|
515
|
+
? canAutoOpenSidebar
|
|
516
|
+
: isTargetPresent || hasBeforeAction;
|
|
517
|
+
const hasRevealPath = isSidebarOnly || hasBeforeAction || canAutoOpenSidebar || isTargetPresent;
|
|
518
|
+
const state = deriveSelectorState({
|
|
519
|
+
isSidebarOnly,
|
|
520
|
+
isAnchorReachable,
|
|
521
|
+
isTargetVisible,
|
|
522
|
+
hasRevealPath,
|
|
523
|
+
});
|
|
524
|
+
const isDisabled = state === "disabled";
|
|
525
|
+
// Reset active (green) state WITHOUT running the undo steps. Used when the
|
|
526
|
+
// revealed container was closed by some other means.
|
|
527
|
+
const resetActiveState = useCallback(() => {
|
|
528
|
+
clearActiveReveal(controllerIdRef.current);
|
|
529
|
+
revealRef.current = null;
|
|
530
|
+
setIsActiveReveal(false);
|
|
531
|
+
}, []);
|
|
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).
|
|
534
|
+
const runRestore = useCallback(async () => {
|
|
535
|
+
const reveal = revealRef.current;
|
|
536
|
+
// Reset button state immediately (button turns blue) while the surface
|
|
537
|
+
// teardown animation plays out; the returned promise resolves once settled.
|
|
538
|
+
clearActiveReveal(reveal?.id ?? controllerIdRef.current);
|
|
539
|
+
revealRef.current = null;
|
|
540
|
+
setIsActiveReveal(false);
|
|
541
|
+
if (reveal) {
|
|
542
|
+
try {
|
|
543
|
+
await reveal.restore();
|
|
544
|
+
}
|
|
545
|
+
catch {
|
|
546
|
+
// ignore restore failures
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}, []);
|
|
550
|
+
const runRestoreRef = useRef(runRestore);
|
|
551
|
+
runRestoreRef.current = runRestore;
|
|
552
|
+
// Keep the presence/visibility probes fresh.
|
|
287
553
|
useEffect(() => {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
554
|
+
const check = () => {
|
|
555
|
+
setIsTargetPresent(targetSelector ? isElementPresent(targetSelector) : false);
|
|
556
|
+
setIsTargetVisible(targetSelector ? isElementVisibleBySelector(targetSelector) : false);
|
|
557
|
+
setIsAnchorPresent(anchorSelector ? isElementPresent(anchorSelector) : false);
|
|
558
|
+
};
|
|
559
|
+
check();
|
|
560
|
+
const attributeFilter = [
|
|
561
|
+
"data-testid",
|
|
562
|
+
"class",
|
|
563
|
+
"id",
|
|
564
|
+
"style",
|
|
565
|
+
"hidden",
|
|
566
|
+
"aria-hidden",
|
|
567
|
+
];
|
|
568
|
+
const observer = new MutationObserver(check);
|
|
569
|
+
observer.observe(document.body, {
|
|
570
|
+
childList: true,
|
|
571
|
+
subtree: true,
|
|
572
|
+
attributes: true,
|
|
573
|
+
attributeFilter,
|
|
574
|
+
});
|
|
575
|
+
let iframeObserver = null;
|
|
576
|
+
const iframe = document.querySelector("iframe.page-iframe");
|
|
577
|
+
if (iframe?.contentDocument?.body) {
|
|
578
|
+
iframeObserver = new MutationObserver(check);
|
|
579
|
+
iframeObserver.observe(iframe.contentDocument.body, {
|
|
580
|
+
childList: true,
|
|
581
|
+
subtree: true,
|
|
582
|
+
attributes: true,
|
|
583
|
+
attributeFilter,
|
|
584
|
+
});
|
|
292
585
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
586
|
+
const intervalId = setInterval(check, 1000);
|
|
587
|
+
return () => {
|
|
588
|
+
observer.disconnect();
|
|
589
|
+
iframeObserver?.disconnect();
|
|
590
|
+
clearInterval(intervalId);
|
|
591
|
+
};
|
|
592
|
+
}, [targetSelector, anchorSelector]);
|
|
593
|
+
// While active, detect if the revealed container was closed externally and
|
|
594
|
+
// reset the button's active (green) state accordingly.
|
|
595
|
+
useEffect(() => {
|
|
596
|
+
if (!isActiveReveal)
|
|
597
|
+
return;
|
|
598
|
+
const check = () => {
|
|
599
|
+
const reveal = revealRef.current;
|
|
600
|
+
if (!reveal || !reveal.isStillOpen()) {
|
|
601
|
+
resetActiveState();
|
|
602
|
+
}
|
|
300
603
|
};
|
|
301
|
-
|
|
302
|
-
const observer = new MutationObserver(checkAvailability);
|
|
604
|
+
const observer = new MutationObserver(check);
|
|
303
605
|
observer.observe(document.body, {
|
|
304
606
|
childList: true,
|
|
305
607
|
subtree: true,
|
|
306
608
|
attributes: true,
|
|
307
|
-
attributeFilter: [
|
|
609
|
+
attributeFilter: [
|
|
610
|
+
EDITOR_FORM_HIDDEN_ATTRIBUTE,
|
|
611
|
+
"class",
|
|
612
|
+
"style",
|
|
613
|
+
"data-testid",
|
|
614
|
+
],
|
|
308
615
|
});
|
|
309
|
-
|
|
310
|
-
const intervalId = setInterval(checkAvailability, 1000);
|
|
616
|
+
const intervalId = setInterval(check, 500);
|
|
311
617
|
return () => {
|
|
312
618
|
observer.disconnect();
|
|
313
619
|
clearInterval(intervalId);
|
|
314
620
|
};
|
|
315
|
-
}, [
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
621
|
+
}, [isActiveReveal, resetActiveState]);
|
|
622
|
+
// Clear the global active controller if this button unmounts while active.
|
|
623
|
+
useEffect(() => {
|
|
624
|
+
return () => {
|
|
625
|
+
clearActiveReveal(controllerIdRef.current);
|
|
626
|
+
};
|
|
627
|
+
}, []);
|
|
628
|
+
const tooltipText = isActiveReveal
|
|
629
|
+
? CLOSE_PANE_LABEL
|
|
630
|
+
: isDisabled
|
|
631
|
+
? selectorDef.notFoundMessage || selectorDef.description
|
|
632
|
+
: selectorDef.description;
|
|
633
|
+
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsxs("button", { "aria-label": isActiveReveal ? CLOSE_PANE_LABEL : "Show me", onClick: async () => {
|
|
634
|
+
// Second click while active -> restore (close=click behaviour).
|
|
635
|
+
if (isActiveReveal) {
|
|
636
|
+
runRestore();
|
|
321
637
|
return;
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
638
|
+
}
|
|
639
|
+
if (isDisabled)
|
|
640
|
+
return;
|
|
641
|
+
// Sequence overlapping Show-me clicks across ALL buttons so a rapid
|
|
642
|
+
// second click can't interleave with this one. We chain behind any
|
|
643
|
+
// in-flight reveal and take a monotonic token; if a newer click
|
|
644
|
+
// arrives while we wait our turn, this (now stale) handler bails out
|
|
645
|
+
// instead of overtaking it. This guarantees "last click wins" and
|
|
646
|
+
// closes the teardown-window race where, during
|
|
647
|
+
// `await superseded.close()`, no global controller is registered.
|
|
648
|
+
const clickToken = ++revealClickSeq;
|
|
649
|
+
const previousClick = revealClickChain;
|
|
650
|
+
let releaseClick = () => { };
|
|
651
|
+
revealClickChain = new Promise((resolve) => {
|
|
652
|
+
releaseClick = resolve;
|
|
653
|
+
});
|
|
654
|
+
try {
|
|
655
|
+
await previousClick;
|
|
656
|
+
// A newer click superseded this one while we waited our turn.
|
|
657
|
+
if (clickToken !== revealClickSeq) {
|
|
327
658
|
return;
|
|
328
659
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
660
|
+
// 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).
|
|
666
|
+
const superseded = supersedeActiveReveal(controllerIdRef.current);
|
|
667
|
+
if (superseded) {
|
|
668
|
+
try {
|
|
669
|
+
// Await the close so A's surface finishes its teardown
|
|
670
|
+
// animation before B starts revealing (event-driven, not timed).
|
|
671
|
+
await superseded.close();
|
|
672
|
+
}
|
|
673
|
+
catch {
|
|
674
|
+
// ignore supersede failures
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
const runId = globalThis.crypto?.randomUUID?.() ??
|
|
678
|
+
`manual-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
679
|
+
const resolveElements = (selector = selectorDef.selector) => selector ? getElementsForSelector(selector) : [];
|
|
680
|
+
const runManualAction = async (actionName, elements) => {
|
|
681
|
+
if (!actionName ||
|
|
682
|
+
!editContext ||
|
|
683
|
+
!manualActions?.[actionName]) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
const action = manualActions[actionName];
|
|
687
|
+
const actionProps = {
|
|
688
|
+
editContext,
|
|
689
|
+
selectorDef,
|
|
690
|
+
duration: HIGHLIGHT_DURATION_MS,
|
|
691
|
+
runId,
|
|
692
|
+
elements,
|
|
693
|
+
resolveElements,
|
|
694
|
+
};
|
|
695
|
+
await action(actionProps);
|
|
337
696
|
};
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
697
|
+
// Undo steps captured as we reveal, plus predicates telling
|
|
698
|
+
// whether the revealed container is still open.
|
|
699
|
+
const undoSteps = [];
|
|
700
|
+
const openPredicates = [];
|
|
701
|
+
// 1. Sidebar-only: open the sidebar.
|
|
702
|
+
if (isSidebarOnly &&
|
|
703
|
+
editContext?.openSidebar &&
|
|
704
|
+
selectorDef.location) {
|
|
705
|
+
const sidebarId = selectorDef.location;
|
|
706
|
+
const wasOpen = !!editContext.openSidebars?.includes(sidebarId);
|
|
707
|
+
if (!wasOpen) {
|
|
708
|
+
editContext.openSidebar(sidebarId);
|
|
709
|
+
undoSteps.push(async () => {
|
|
710
|
+
editContextRef.current?.toggleSidebar?.(sidebarId, {
|
|
711
|
+
forceClose: true,
|
|
712
|
+
});
|
|
713
|
+
await editContextRef.current?.waitForSurfaceSettled?.(`sidebar:${sidebarId}`);
|
|
714
|
+
});
|
|
715
|
+
// Wait for the sidebar's open animation to finish before querying.
|
|
716
|
+
await editContext.waitForSurfaceSettled?.(`sidebar:${sidebarId}`);
|
|
717
|
+
}
|
|
718
|
+
openPredicates.push(() => !!editContextRef.current?.openSidebars?.includes(sidebarId));
|
|
719
|
+
}
|
|
720
|
+
const availabilityElements = anchorSelector
|
|
721
|
+
? resolveElements(anchorSelector)
|
|
722
|
+
: [];
|
|
723
|
+
const relevantElements = !isSidebarOnly
|
|
724
|
+
? [...resolveElements(), ...availabilityElements]
|
|
725
|
+
: availabilityElements;
|
|
726
|
+
const formSlider = getEditorFormSlider(relevantElements);
|
|
727
|
+
const shouldOpenEditorForm = !isSidebarOnly &&
|
|
728
|
+
!!formSlider &&
|
|
729
|
+
isEditorFormSliderHidden(formSlider);
|
|
730
|
+
if (selectorDef.tab) {
|
|
731
|
+
editContext?.setActiveEditorTab(selectorDef.tab);
|
|
732
|
+
}
|
|
733
|
+
// 2. Editor form slider.
|
|
734
|
+
if (shouldOpenEditorForm && editContext) {
|
|
735
|
+
const slotId = editContext.getActiveSlotId();
|
|
736
|
+
editContext.setEditorFormHiddenForSlot(slotId, false);
|
|
737
|
+
undoSteps.push(async () => {
|
|
738
|
+
editContextRef.current?.setEditorFormHiddenForSlot(slotId, true);
|
|
739
|
+
await editContextRef.current?.waitForSurfaceSettled?.("editor-form");
|
|
740
|
+
});
|
|
741
|
+
openPredicates.push(() => !isEditorFormSliderHidden(getEditorFormSlider()));
|
|
742
|
+
await editContext.waitForSurfaceSettled?.("editor-form");
|
|
743
|
+
}
|
|
744
|
+
else if (selectorDef.tab) {
|
|
745
|
+
await waitForNextFrame();
|
|
746
|
+
}
|
|
747
|
+
// 3. beforeAction (may open the More-panels flyout, reveal fields, etc.).
|
|
748
|
+
const resolvedElements = !isSidebarOnly
|
|
749
|
+
? await waitForResolvedElements(resolveElements, selectorDef.selector)
|
|
750
|
+
: [];
|
|
751
|
+
const actionElements = resolvedElements.length > 0
|
|
752
|
+
? resolvedElements
|
|
753
|
+
: availabilityElements;
|
|
754
|
+
const morePanelsWasOpen = isElementPresent("@more-sidebars-panel");
|
|
755
|
+
// Capture pre-reveal state for the AI Assistant reveals so we only
|
|
756
|
+
// restore (close) what THIS button actually opened.
|
|
757
|
+
const agentsPanelWasOpen = isAgentsPanelOpen();
|
|
758
|
+
const agentSettingsWasOpen = !!selectorDef.selector &&
|
|
759
|
+
isElementPresent(selectorDef.selector) &&
|
|
760
|
+
selectorDef.beforeAction === "open-agent-settings";
|
|
761
|
+
await runManualAction(selectorDef.beforeAction, actionElements);
|
|
762
|
+
if (selectorDef.beforeAction === "open-more-sidebars") {
|
|
763
|
+
const morePanelsNowOpen = isElementPresent("@more-sidebars-panel");
|
|
764
|
+
if (!morePanelsWasOpen && morePanelsNowOpen) {
|
|
765
|
+
undoSteps.push(async () => {
|
|
766
|
+
closeMorePanelsIfOpen();
|
|
767
|
+
await waitForSelectorAnimations('[data-testid="more-sidebars-panel"]');
|
|
768
|
+
});
|
|
769
|
+
openPredicates.push(() => isElementPresent("@more-sidebars-panel"));
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (selectorDef.beforeAction === "open-agents-panel" ||
|
|
773
|
+
selectorDef.beforeAction === "open-agent-settings") {
|
|
774
|
+
// The beforeAction already awaited the panel/popover open
|
|
775
|
+
// animation (see config openAgentsPanel/openAgentSettings), so
|
|
776
|
+
// no polling here.
|
|
777
|
+
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.
|
|
780
|
+
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
|
+
}
|
|
792
|
+
});
|
|
793
|
+
openPredicates.push(() => isAgentsPanelOpen());
|
|
794
|
+
}
|
|
795
|
+
if (selectorDef.beforeAction === "open-agent-settings") {
|
|
796
|
+
const settingsTargetNowOpen = !!selectorDef.selector &&
|
|
797
|
+
isElementPresent(selectorDef.selector);
|
|
798
|
+
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
|
+
}
|
|
807
|
+
});
|
|
808
|
+
openPredicates.push(() => selectorDef.selector
|
|
809
|
+
? isElementPresent(selectorDef.selector)
|
|
810
|
+
: false);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
// afterAction is the declared inverse for custom reveals (e.g. field actions).
|
|
815
|
+
if (selectorDef.afterAction) {
|
|
816
|
+
undoSteps.push(async () => {
|
|
817
|
+
await runManualAction(selectorDef.afterAction, !isSidebarOnly ? resolveElements() : availabilityElements);
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
// Highlight the target. For sidebar-only selectors there is no
|
|
821
|
+
// inner target, so we highlight the opened sidebar panel itself.
|
|
822
|
+
// Wait for it to become visible first, since a just-revealed
|
|
823
|
+
// element may be present but not yet laid out (highlightElement
|
|
824
|
+
// skips invisible ones).
|
|
825
|
+
const highlightSelector = isSidebarOnly
|
|
826
|
+
? selectorDef.location
|
|
827
|
+
? `@sidebar-panel-${selectorDef.location}`
|
|
828
|
+
: ""
|
|
829
|
+
: selectorDef.selector;
|
|
830
|
+
// The highlight runs a finite CSS animation; highlightElement
|
|
831
|
+
// resolves on its animationend. We do NOT await it before
|
|
832
|
+
// registering the reveal (the button must turn green immediately);
|
|
833
|
+
// close=auto instead chains its restore onto this lifetime.
|
|
834
|
+
let highlightLifetime = Promise.resolve();
|
|
835
|
+
if (highlightSelector) {
|
|
836
|
+
const highlightTargets = await waitForVisibleElements(highlightSelector, 1500);
|
|
837
|
+
if (highlightTargets.length > 0) {
|
|
838
|
+
highlightLifetime = highlightElement(highlightSelector, HIGHLIGHT_DURATION_MS);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
// If we opened/undid anything, this was a State-3 reveal: keep the
|
|
842
|
+
// button active (green) and register it for mutual exclusion.
|
|
843
|
+
if (undoSteps.length > 0) {
|
|
844
|
+
const id = ++revealControllerSeq;
|
|
845
|
+
controllerIdRef.current = id;
|
|
846
|
+
revealRef.current = {
|
|
847
|
+
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
|
+
}
|
|
857
|
+
},
|
|
858
|
+
isStillOpen: () => openPredicates.length === 0
|
|
859
|
+
? true
|
|
860
|
+
: openPredicates.some((predicate) => predicate()),
|
|
861
|
+
};
|
|
862
|
+
registerActiveReveal({
|
|
863
|
+
id,
|
|
864
|
+
close: () => runRestoreRef.current(),
|
|
865
|
+
isStillOpen: () => revealRef.current?.isStillOpen() ?? false,
|
|
866
|
+
});
|
|
867
|
+
setIsActiveReveal(true);
|
|
868
|
+
if (closeMode === "auto") {
|
|
869
|
+
// Restore right after the highlight animation completes,
|
|
870
|
+
// instead of racing a duration-matched timer.
|
|
871
|
+
void highlightLifetime.then(() => {
|
|
872
|
+
if (controllerIdRef.current === id) {
|
|
873
|
+
void runRestoreRef.current();
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
}
|
|
351
877
|
}
|
|
352
878
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
await highlightElement(selectorDef.selector, HIGHLIGHT_DURATION_MS);
|
|
359
|
-
}
|
|
360
|
-
if (selectorDef.afterAction) {
|
|
361
|
-
window.setTimeout(() => {
|
|
362
|
-
void runManualAction(selectorDef.afterAction, !isSidebarOnly ? resolveElements() : []);
|
|
363
|
-
}, HIGHLIGHT_DURATION_MS);
|
|
879
|
+
finally {
|
|
880
|
+
// Release our slot so the next queued click can proceed (and then
|
|
881
|
+
// properly supersede us). Runs even if the reveal threw, so the
|
|
882
|
+
// click chain can never deadlock.
|
|
883
|
+
releaseClick();
|
|
364
884
|
}
|
|
365
|
-
}, disabled: !
|
|
366
|
-
? "border-feedback-
|
|
367
|
-
:
|
|
885
|
+
}, disabled: isDisabled && !isActiveReveal, className: cn("inline-flex h-5 w-5 items-center justify-center rounded border transition-colors", isActiveReveal
|
|
886
|
+
? "border-feedback-green bg-feedback-green-light text-feedback-green hover:bg-feedback-green-light cursor-pointer"
|
|
887
|
+
: !isDisabled
|
|
888
|
+
? "border-feedback-blue bg-feedback-blue-light text-feedback-blue hover:bg-feedback-blue-light cursor-pointer"
|
|
889
|
+
: "border-border-default bg-neutral-grey-5 text-neutral-grey-50 cursor-not-allowed"), children: [_jsx(LocateFixed, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }), _jsx("span", { className: "sr-only", children: "Show me" })] }, keyProp) }), _jsx(TooltipContent, { children: tooltipText })] }));
|
|
368
890
|
}
|
|
369
891
|
// Render markdown content with clickable selectors
|
|
370
892
|
// Supports both {{selectorName}} syntax (looks up in uiSelectors) and legacy @selector syntax
|