@plannotator/ui 0.52.1 → 0.53.1
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/HANDOFF.md +284 -29
- package/README.md +45 -5
- package/components/AnnotationPanel.tsx +195 -118
- package/components/AnnotationToolbar.tsx +17 -14
- package/components/AnnotationToolstrip.tsx +54 -40
- package/components/BlockRenderer.tsx +20 -14
- package/components/CodeFilePopout.tsx +132 -69
- package/components/CommentPopover.tsx +8 -349
- package/components/ComposerTokenTextarea.tsx +379 -0
- package/components/ExportModal.tsx +6 -1
- package/components/HostTitle.tsx +108 -0
- package/components/HtmlSurfaceControls.tsx +2 -1
- package/components/ImageLightbox.tsx +288 -17
- package/components/ImagePeek.tsx +255 -0
- package/components/ImageResolverContext.tsx +31 -0
- package/components/InlineMarkdown.tsx +123 -15
- package/components/LocalLinkContext.tsx +49 -0
- package/components/MentionPicker.tsx +51 -0
- package/components/MenuVersionSection.tsx +51 -25
- package/components/OpenInAppButton.tsx +18 -49
- package/components/PlanHeaderMenu.tsx +2 -8
- package/components/ProviderIcons.tsx +10 -4
- package/components/RenderedMarkdown.tsx +40 -4
- package/components/Settings.tsx +33 -5
- package/components/SkillReferenceMenu.tsx +67 -18
- package/components/Tooltip.tsx +51 -4
- package/components/Viewer.tsx +220 -49
- package/components/ai/AIProviderBar.tsx +11 -0
- package/components/ai/AskSessionSetupHint.tsx +12 -0
- package/components/ai/DocumentAIChatPanel.tsx +17 -5
- package/components/ai/SessionAskNotice.tsx +4 -3
- package/components/blocks/BlockquoteBlock.tsx +159 -0
- package/components/blocks/HtmlBlock.tsx +121 -9
- package/components/blocks/QuestionBlock.tsx +183 -96
- package/components/blocks/QuestionContext.tsx +3 -0
- package/components/diagram/DiagramViewer.tsx +41 -4
- package/components/diagram/index.ts +1 -1
- package/components/html-viewer/HtmlViewer.tsx +95 -3
- package/components/html-viewer/bridge-script.asset.js +413 -7
- package/components/html-viewer/bridge-script.lite.ts +1 -1
- package/components/html-viewer/bridge-script.ts +454 -7
- package/components/html-viewer/useHtmlAnnotation.ts +51 -3
- package/components/icons/AgentIcons.tsx +9 -7
- package/components/plan-diff/PlanCleanDiffView.tsx +239 -36
- package/components/plan-diff/PlanDiffViewer.tsx +6 -1
- package/configure.ts +27 -1
- package/hooks/useAgentJobs.ts +8 -3
- package/hooks/useAnnotationHighlighter.ts +128 -23
- package/hooks/useExternalAnnotations.ts +6 -0
- package/hooks/useMentionAutocomplete.ts +1 -1
- package/hooks/useOpenInApps.ts +88 -0
- package/hooks/useSkillReferenceAutocomplete.ts +7 -3
- package/hooks/useUpdateCheck.ts +38 -98
- package/hooks/useUpdateCheckSetting.ts +43 -0
- package/hooks/useUpdateToast.ts +101 -0
- package/hooks/useVimSelection.ts +9 -0
- package/package.json +2 -2
- package/shortcuts/code-review/markdownPreview.shortcuts.ts +18 -0
- package/shortcuts/index.ts +1 -0
- package/shortcuts/runtime.ts +10 -3
- package/styles.css +1 -1
- package/types.ts +48 -0
- package/utils/agentClosed.ts +26 -0
- package/utils/agentToolSetting.ts +16 -9
- package/utils/aiProvider.ts +7 -0
- package/utils/askSessionSetupHint.ts +27 -0
- package/utils/blockTargeting.ts +14 -3
- package/utils/mentions.ts +34 -5
- package/utils/parser.ts +65 -51
- package/utils/pasteInsert.ts +234 -0
- package/utils/planDiffEngine.ts +272 -2
- package/utils/skillCatalog.ts +15 -2
- package/utils/skillReferences.ts +50 -13
- package/utils/storage.ts +5 -0
- package/utils/textCut.ts +41 -0
- package/utils/updateNotice.ts +120 -0
package/components/Settings.tsx
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UpdateCheckSetting } from '../hooks/useUpdateCheckSetting';
|
|
1
2
|
import React, { useState, useEffect, useMemo, useRef } from 'react';
|
|
2
3
|
import { createPortal } from 'react-dom';
|
|
3
4
|
import type { AnnotateAgentTerminalSide } from '@plannotator/core/agent-terminal';
|
|
@@ -112,6 +113,10 @@ interface SettingsProps {
|
|
|
112
113
|
* `env` is PLANNOTATOR_AUTO_UPDATE when it overrides the config file.
|
|
113
114
|
* Hosts that omit it get no toggle. */
|
|
114
115
|
autoUpdateSetting?: { env?: boolean };
|
|
116
|
+
/** Present when the server reports the update-notice switch (serverConfig.updateCheck,
|
|
117
|
+
* useUpdateCheckSetting); `env` is PLANNOTATOR_UPDATE_CHECK when it overrides the
|
|
118
|
+
* config file. Hosts that omit it get no row. */
|
|
119
|
+
updateCheckSetting?: UpdateCheckSetting;
|
|
115
120
|
/** The `plannotator` agent tool switch, as the server reports it
|
|
116
121
|
* (parseAgentToolSetting of serverConfig: present only when the session's
|
|
117
122
|
* host registers the tool). Shown only together with onAgentToolChange;
|
|
@@ -1002,9 +1007,10 @@ const CommentsTab: React.FC = () => {
|
|
|
1002
1007
|
);
|
|
1003
1008
|
};
|
|
1004
1009
|
|
|
1005
|
-
export const Settings: React.FC<SettingsProps> = ({ taterMode, onTaterModeChange, onIdentityChange, origin, mode = 'plan', annotateParity = false, onUIPreferencesChange, externalOpen, onExternalClose, aiProviders = [], gitUser, autoUpdateSetting, agentToolSetting, onAgentToolChange, sinceBaseUnavailable, isCompactTouchLayout = false, onDetectObsidianVaults, agentTerminalAvailable = false, webmcpAvailable = false }) => {
|
|
1010
|
+
export const Settings: React.FC<SettingsProps> = ({ taterMode, onTaterModeChange, onIdentityChange, origin, mode = 'plan', annotateParity = false, onUIPreferencesChange, externalOpen, onExternalClose, aiProviders = [], gitUser, autoUpdateSetting, updateCheckSetting, agentToolSetting, onAgentToolChange, sinceBaseUnavailable, isCompactTouchLayout = false, onDetectObsidianVaults, agentTerminalAvailable = false, webmcpAvailable = false }) => {
|
|
1006
1011
|
const webmcpTools = useWebMcpToolsEnabled();
|
|
1007
1012
|
const autoUpdate = useConfigValue('autoUpdate');
|
|
1013
|
+
const updateChecksOn = updateCheckSetting ? updateCheckSetting.on : true;
|
|
1008
1014
|
const [showDialog, setShowDialog] = useState(false);
|
|
1009
1015
|
const settingsWasOpenRef = useRef(false);
|
|
1010
1016
|
const [themePreview, setThemePreview] = useState(false);
|
|
@@ -1441,20 +1447,42 @@ export const Settings: React.FC<SettingsProps> = ({ taterMode, onTaterModeChange
|
|
|
1441
1447
|
</div>
|
|
1442
1448
|
</div>
|
|
1443
1449
|
|
|
1450
|
+
{/* Update notices: on by default. Writes updateCheck to
|
|
1451
|
+
~/.plannotator/config.json; off makes no request to GitHub. */}
|
|
1452
|
+
{updateCheckSetting && (
|
|
1453
|
+
<>
|
|
1454
|
+
<div className="border-t border-border" />
|
|
1455
|
+
<ToggleSwitch
|
|
1456
|
+
checked={updateChecksOn}
|
|
1457
|
+
onChange={(v) => updateCheckSetting.onChange(v)}
|
|
1458
|
+
disabled={updateCheckSetting.env !== undefined}
|
|
1459
|
+
label="Tell me when a new version is out"
|
|
1460
|
+
description={
|
|
1461
|
+
updateCheckSetting.env !== undefined
|
|
1462
|
+
? 'Set by PLANNOTATOR_UPDATE_CHECK in your environment.'
|
|
1463
|
+
: 'This computer asks GitHub once a day, never your browser. Off makes no request at all.'
|
|
1464
|
+
}
|
|
1465
|
+
/>
|
|
1466
|
+
</>
|
|
1467
|
+
)}
|
|
1468
|
+
|
|
1444
1469
|
{/* Auto-update (#1634): opt-in, default off. Writes
|
|
1445
|
-
autoUpdate to ~/.plannotator/config.json.
|
|
1470
|
+
autoUpdate to ~/.plannotator/config.json. It needs
|
|
1471
|
+
update checks, so it is locked while they are off. */}
|
|
1446
1472
|
{autoUpdateSetting && (
|
|
1447
1473
|
<>
|
|
1448
1474
|
<div className="border-t border-border" />
|
|
1449
1475
|
<ToggleSwitch
|
|
1450
|
-
checked={autoUpdateSetting.env ?? autoUpdate}
|
|
1476
|
+
checked={(autoUpdateSetting.env ?? autoUpdate) && updateChecksOn}
|
|
1451
1477
|
onChange={(v) => configStore.set('autoUpdate', v)}
|
|
1452
|
-
disabled={autoUpdateSetting.env !== undefined}
|
|
1478
|
+
disabled={autoUpdateSetting.env !== undefined || !updateChecksOn}
|
|
1453
1479
|
label="Keep Plannotator up to date"
|
|
1454
1480
|
description={
|
|
1455
1481
|
autoUpdateSetting.env !== undefined
|
|
1456
1482
|
? 'Set by PLANNOTATOR_AUTO_UPDATE in your environment.'
|
|
1457
|
-
:
|
|
1483
|
+
: !updateChecksOn
|
|
1484
|
+
? 'Needs update checks (above).'
|
|
1485
|
+
: 'Installs new releases in the background, at most once a day, never while another review is open.'
|
|
1458
1486
|
}
|
|
1459
1487
|
/>
|
|
1460
1488
|
</>
|
|
@@ -15,6 +15,16 @@ interface SkillReferenceMenuProps {
|
|
|
15
15
|
/** Explicitly activated row, or null — the menu opens with nothing active. */
|
|
16
16
|
activeIndex: number | null;
|
|
17
17
|
onSelect: (index: number) => void;
|
|
18
|
+
/**
|
|
19
|
+
* The box the menu must stay inside, in viewport coordinates, read at each
|
|
20
|
+
* measure. Default: the viewport. A host whose composer sits in a dialog
|
|
21
|
+
* with a footer passes the dialog's area above the footer.
|
|
22
|
+
*/
|
|
23
|
+
boundary?: () => { top: number; bottom: number } | null;
|
|
24
|
+
/** Which side of the anchor the menu tries first. Default: above. */
|
|
25
|
+
prefer?: 'above' | 'below';
|
|
26
|
+
/** Upper bound on the list's height, in px. Default: 256. */
|
|
27
|
+
maxListHeight?: number;
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
/** Vertical gap between the composer wrapper and the menu (mb-1.5 / mt-1.5 = 6px). */
|
|
@@ -40,24 +50,29 @@ interface MenuPlacement {
|
|
|
40
50
|
* When neither side fits the whole list, the side with more room wins and the
|
|
41
51
|
* list is clamped to it, so the menu never extends past a viewport edge.
|
|
42
52
|
*/
|
|
43
|
-
function computeMenuPlacement(
|
|
53
|
+
export function computeMenuPlacement(
|
|
44
54
|
anchorRect: Pick<DOMRect, 'top' | 'bottom'>,
|
|
45
55
|
naturalListHeight: number,
|
|
46
56
|
chromeHeight: number,
|
|
47
57
|
viewportHeight: number,
|
|
58
|
+
options: { boundaryTop?: number; prefer?: 'above' | 'below'; maxListHeight?: number } = {},
|
|
48
59
|
): MenuPlacement {
|
|
49
|
-
const
|
|
60
|
+
const boundaryTop = options.boundaryTop ?? 0;
|
|
61
|
+
const cap = options.maxListHeight ?? MAX_LIST_HEIGHT;
|
|
62
|
+
const spaceAbove = anchorRect.top - boundaryTop - MENU_GAP - MENU_VIEWPORT_MARGIN - chromeHeight;
|
|
50
63
|
const spaceBelow = viewportHeight - anchorRect.bottom - MENU_GAP - MENU_VIEWPORT_MARGIN - chromeHeight;
|
|
51
|
-
const needed = Math.min(naturalListHeight,
|
|
64
|
+
const needed = Math.min(naturalListHeight, cap);
|
|
65
|
+
const [first, second] = options.prefer === 'below' ? (['below', 'above'] as const) : (['above', 'below'] as const);
|
|
66
|
+
const space = (side: MenuPlacement['direction']) => (side === 'above' ? spaceAbove : spaceBelow);
|
|
52
67
|
const direction: MenuPlacement['direction'] =
|
|
53
|
-
needed <=
|
|
54
|
-
: needed <=
|
|
55
|
-
:
|
|
56
|
-
:
|
|
57
|
-
const available = direction
|
|
68
|
+
needed <= space(first) ? first
|
|
69
|
+
: needed <= space(second) ? second
|
|
70
|
+
: space(first) >= space(second) ? first
|
|
71
|
+
: second;
|
|
72
|
+
const available = space(direction);
|
|
58
73
|
return {
|
|
59
74
|
direction,
|
|
60
|
-
maxListHeight: Math.max(0, Math.min(
|
|
75
|
+
maxListHeight: Math.max(0, Math.min(cap, Math.round(available))),
|
|
61
76
|
};
|
|
62
77
|
}
|
|
63
78
|
|
|
@@ -85,13 +100,19 @@ export const SkillReferenceMenu: React.FC<SkillReferenceMenuProps> = ({
|
|
|
85
100
|
items,
|
|
86
101
|
activeIndex,
|
|
87
102
|
onSelect,
|
|
103
|
+
boundary,
|
|
104
|
+
prefer,
|
|
105
|
+
maxListHeight,
|
|
88
106
|
}) => {
|
|
89
107
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
90
108
|
const listRef = useRef<HTMLDivElement>(null);
|
|
91
109
|
const [placement, setPlacement] = useState<MenuPlacement>({
|
|
92
|
-
direction: 'above',
|
|
93
|
-
maxListHeight: MAX_LIST_HEIGHT,
|
|
110
|
+
direction: prefer ?? 'above',
|
|
111
|
+
maxListHeight: maxListHeight ?? MAX_LIST_HEIGHT,
|
|
94
112
|
});
|
|
113
|
+
// The optional seams, read through a ref so `measure` stays stable for its listeners.
|
|
114
|
+
const optionsRef = useRef({ boundary, prefer, maxListHeight });
|
|
115
|
+
optionsRef.current = { boundary, prefer, maxListHeight };
|
|
95
116
|
|
|
96
117
|
const measure = useCallback(() => {
|
|
97
118
|
const menuEl = menuRef.current;
|
|
@@ -106,7 +127,13 @@ export const SkillReferenceMenu: React.FC<SkillReferenceMenuProps> = ({
|
|
|
106
127
|
// jitter loop this component once shipped.
|
|
107
128
|
const chrome = Math.max(0, menuEl.offsetHeight - listEl.offsetHeight);
|
|
108
129
|
// scrollHeight reports full content height even while clamped.
|
|
109
|
-
const
|
|
130
|
+
const { boundary: readBoundary, prefer: side, maxListHeight: cap } = optionsRef.current;
|
|
131
|
+
const box = readBoundary?.() ?? null;
|
|
132
|
+
const next = computeMenuPlacement(rect, listEl.scrollHeight, chrome, box ? box.bottom : window.innerHeight, {
|
|
133
|
+
boundaryTop: box ? box.top : 0,
|
|
134
|
+
prefer: side,
|
|
135
|
+
maxListHeight: cap,
|
|
136
|
+
});
|
|
110
137
|
setPlacement((prev) =>
|
|
111
138
|
prev.direction === next.direction && prev.maxListHeight === next.maxListHeight
|
|
112
139
|
? prev
|
|
@@ -182,11 +209,12 @@ export const SkillReferenceMenu: React.FC<SkillReferenceMenuProps> = ({
|
|
|
182
209
|
}`}
|
|
183
210
|
>
|
|
184
211
|
<SkillIcon />
|
|
185
|
-
|
|
186
|
-
<span className="
|
|
212
|
+
{/* data-skill-item-part: a host's styling hook (the Inbox lays a row out in two lines on a phone). */}
|
|
213
|
+
<span data-skill-item-part="name" className="shrink-0 font-semibold text-foreground">{item.name}</span>
|
|
214
|
+
<span data-skill-item-part="description" className="min-w-0 flex-1 truncate text-muted-foreground">
|
|
187
215
|
{item.description ?? ''}
|
|
188
216
|
</span>
|
|
189
|
-
<span className="shrink-0 pl-3 text-xs text-muted-foreground/80">
|
|
217
|
+
<span data-skill-item-part="root" className="shrink-0 pl-3 text-xs text-muted-foreground/80">
|
|
190
218
|
{ROOT_LABELS[item.root]}
|
|
191
219
|
</span>
|
|
192
220
|
</button>
|
|
@@ -215,6 +243,26 @@ const SkillIcon: React.FC = () => (
|
|
|
215
243
|
|
|
216
244
|
interface HumanOnlySkillNoticeProps {
|
|
217
245
|
skills: SkillCatalogEntry[];
|
|
246
|
+
/**
|
|
247
|
+
* What the instructions go with. Default: the host's (`setSkillInstructionsDestination`),
|
|
248
|
+
* else "your feedback". The Inbox says "your reply" or "your message".
|
|
249
|
+
*/
|
|
250
|
+
destination?: string;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Module-level seam (see packages/ui/CLAUDE.md): what every composer's
|
|
254
|
+
// human-only line says the instructions go with, unless the composer says.
|
|
255
|
+
// Plannotator's comments go with "your feedback"; the Inbox's comments on
|
|
256
|
+
// attached files ride a reply.
|
|
257
|
+
const DEFAULT_DESTINATION = 'your feedback';
|
|
258
|
+
let defaultDestination = DEFAULT_DESTINATION;
|
|
259
|
+
|
|
260
|
+
export function setSkillInstructionsDestination(destination: string): void {
|
|
261
|
+
defaultDestination = destination || DEFAULT_DESTINATION;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function resetSkillInstructionsDestination(): void {
|
|
265
|
+
defaultDestination = DEFAULT_DESTINATION;
|
|
218
266
|
}
|
|
219
267
|
|
|
220
268
|
/**
|
|
@@ -227,8 +275,9 @@ interface HumanOnlySkillNoticeProps {
|
|
|
227
275
|
* toggles), and assistive tech alike. It pairs with the dotted-underline
|
|
228
276
|
* marker the composer overlay paints on human-only tokens.
|
|
229
277
|
*/
|
|
230
|
-
export const HumanOnlySkillNotice: React.FC<HumanOnlySkillNoticeProps> = ({ skills }) => {
|
|
278
|
+
export const HumanOnlySkillNotice: React.FC<HumanOnlySkillNoticeProps> = ({ skills, destination }) => {
|
|
231
279
|
if (skills.length === 0) return null;
|
|
280
|
+
destination ??= defaultDestination;
|
|
232
281
|
const names = skills.map((s) => s.name).join(', ');
|
|
233
282
|
return (
|
|
234
283
|
<details data-skill-human-only-notice="true" className="group mt-1 px-1">
|
|
@@ -240,12 +289,12 @@ export const HumanOnlySkillNotice: React.FC<HumanOnlySkillNoticeProps> = ({ skil
|
|
|
240
289
|
{skills.length === 1 ? (
|
|
241
290
|
<>
|
|
242
291
|
<span className="font-mono">{names}</span> cannot be invoked by a model, so
|
|
243
|
-
its instructions will be included with
|
|
292
|
+
its instructions will be included with {destination}.
|
|
244
293
|
</>
|
|
245
294
|
) : (
|
|
246
295
|
<>
|
|
247
296
|
<span className="font-mono">{names}</span> cannot be invoked by a model, so
|
|
248
|
-
their instructions will be included with
|
|
297
|
+
their instructions will be included with {destination}.
|
|
249
298
|
</>
|
|
250
299
|
)}
|
|
251
300
|
</p>
|
package/components/Tooltip.tsx
CHANGED
|
@@ -44,6 +44,25 @@ interface TooltipProps {
|
|
|
44
44
|
* that use it for short button labels.
|
|
45
45
|
*/
|
|
46
46
|
wide?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Asked each time the tooltip would open, with the trigger element; `false`
|
|
49
|
+
* keeps it closed this time (e.g. open only while the trigger's text is cut
|
|
50
|
+
* off). Unset: it always opens, as before.
|
|
51
|
+
*/
|
|
52
|
+
shouldOpen?: (trigger: Element) => boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Where the popup is portaled. Unset: the document body. A trigger inside a
|
|
55
|
+
* native modal `<dialog>` needs the dialog here, or the popup is drawn
|
|
56
|
+
* under the dialog's top layer.
|
|
57
|
+
*/
|
|
58
|
+
container?: HTMLElement | null;
|
|
59
|
+
/**
|
|
60
|
+
* When true, Escape closes the tooltip without using the key up: it still
|
|
61
|
+
* reaches the page (not default-prevented, not stopped), so the page's own
|
|
62
|
+
* Escape (closing a pane) works while a tooltip is open. Unset: Base UI's
|
|
63
|
+
* default, where the Escape that closes a tooltip goes no further.
|
|
64
|
+
*/
|
|
65
|
+
escapePassesThrough?: boolean;
|
|
47
66
|
}
|
|
48
67
|
|
|
49
68
|
export const Tooltip: React.FC<TooltipProps> = ({
|
|
@@ -54,15 +73,43 @@ export const Tooltip: React.FC<TooltipProps> = ({
|
|
|
54
73
|
delayDuration,
|
|
55
74
|
sideOffset = 8,
|
|
56
75
|
wide = false,
|
|
76
|
+
shouldOpen,
|
|
77
|
+
container,
|
|
78
|
+
escapePassesThrough = false,
|
|
57
79
|
}) => {
|
|
58
80
|
const disableHoverablePopup = React.useContext(DisableHoverablePopupContext);
|
|
81
|
+
// Controlled only for escapePassesThrough: the Escape close is canceled (so Base UI neither prevents nor stops
|
|
82
|
+
// the key) and the tooltip is closed here instead.
|
|
83
|
+
const [controlledOpen, setControlledOpen] = React.useState(false);
|
|
84
|
+
const controlled = escapePassesThrough ? { open: controlledOpen } : {};
|
|
59
85
|
return (
|
|
60
|
-
<BaseTooltip.Root
|
|
86
|
+
<BaseTooltip.Root
|
|
87
|
+
disableHoverablePopup={disableHoverablePopup}
|
|
88
|
+
{...controlled}
|
|
89
|
+
onOpenChange={
|
|
90
|
+
shouldOpen || escapePassesThrough
|
|
91
|
+
? (open, details) => {
|
|
92
|
+
if (open && shouldOpen && !(details.trigger && shouldOpen(details.trigger))) {
|
|
93
|
+
details.cancel();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (!escapePassesThrough) return;
|
|
97
|
+
if (!open && details.reason === 'escape-key') {
|
|
98
|
+
details.cancel();
|
|
99
|
+
details.allowPropagation();
|
|
100
|
+
}
|
|
101
|
+
setControlledOpen(open);
|
|
102
|
+
}
|
|
103
|
+
: undefined
|
|
104
|
+
}
|
|
105
|
+
>
|
|
61
106
|
<BaseTooltip.Trigger render={children} delay={delayDuration} />
|
|
62
|
-
<BaseTooltip.Portal>
|
|
63
|
-
|
|
107
|
+
<BaseTooltip.Portal container={container ?? undefined}>
|
|
108
|
+
{/* Above the annotation toolbar (z-[100]), so a tooltip that flips toward it is never tucked under it. */}
|
|
109
|
+
<BaseTooltip.Positioner side={side} align={align} sideOffset={sideOffset} className="isolate z-[110]">
|
|
64
110
|
<BaseTooltip.Popup
|
|
65
|
-
|
|
111
|
+
data-pn-tooltip=""
|
|
112
|
+
className={`z-[110] px-2 py-1 text-xs bg-popover text-popover-foreground border border-border rounded shadow-md origin-[var(--transform-origin)] transition-[opacity,scale] duration-150 ease-out data-[starting-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[ending-style]:scale-95 ${
|
|
66
113
|
wide
|
|
67
114
|
? 'max-w-[min(260px,calc(100vw-1rem))] leading-snug whitespace-normal [overflow-wrap:anywhere]'
|
|
68
115
|
: 'whitespace-nowrap'
|