@qilin-agent/client-ui-primitives 3.1.7 → 3.1.8
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/README.i18n.yaml +8 -8
- package/README.md +11 -4
- package/README.zh.md +12 -4
- package/lib/AnimatedRows.module.css +7 -0
- package/lib/Button.module.css +6 -3
- package/lib/DetailDocument.module.css +67 -0
- package/lib/HoverCard.module.css +3 -3
- package/lib/ImageLightbox.module.css +1 -1
- package/lib/InlineEditor.module.css +1 -1
- package/lib/Input.module.css +2 -2
- package/lib/Menu.module.css +127 -78
- package/lib/MenuSurface.module.css +23 -7
- package/lib/QuotePreview.module.css +89 -0
- package/lib/RowDragPreview.module.css +12 -0
- package/lib/SegmentedTabs.module.css +4 -1
- package/lib/Tag.module.css +1 -1
- package/lib/TerminalBlock.module.css +1 -1
- package/lib/Toast.module.css +28 -0
- package/lib/UserMessageBubble.module.css +11 -0
- package/lib/index.js +2094 -1115
- package/lib/markdown/CodeBlock.module.css +23 -0
- package/lib/markdown/JsonBlock.module.css +1 -1
- package/lib/markdown/MarkdownText.module.css +11 -7
- package/lib/settings-form/fields.module.css +2 -2
- package/lib/types/AnimatedRows.d.ts +46 -0
- package/lib/types/Button.d.ts +13 -4
- package/lib/types/DetailDocument.d.ts +79 -0
- package/lib/types/ImageLightbox.d.ts +3 -1
- package/lib/types/Menu.d.ts +25 -9
- package/lib/types/MenuSurface.d.ts +9 -0
- package/lib/types/QuotePreview.d.ts +26 -0
- package/lib/types/SegmentedTabs.d.ts +3 -1
- package/lib/types/Toast.d.ts +19 -1
- package/lib/types/Tooltip.d.ts +13 -8
- package/lib/types/UserMessageBubble.d.ts +11 -0
- package/lib/types/code-highlighting.d.ts +12 -0
- package/lib/types/focus.d.ts +8 -0
- package/lib/types/icons/index.d.ts +8 -0
- package/lib/types/icons/shared-artwork.d.ts +1 -0
- package/lib/types/index.d.ts +20 -4
- package/lib/types/markdown/MarkdownDelegate.d.ts +19 -2
- package/lib/types/markdown/MarkdownText.d.ts +3 -1
- package/lib/types/markdown/file-link.d.ts +13 -3
- package/lib/types/markdown/mathCompatibility.d.ts +12 -5
- package/lib/types/markdown/render.d.ts +2 -1
- package/lib/types/markdown/windows-path-destinations.d.ts +12 -0
- package/lib/types/motion.d.ts +13 -0
- package/lib/types/overlay-top-margin.d.ts +10 -2
- package/lib/types/quote-text.d.ts +69 -0
- package/lib/types/use-clipboard-copy.d.ts +14 -0
- package/lib/types/useAnchoredPosition.d.ts +31 -18
- package/lib/types/useRowDragPreview.d.ts +20 -0
- package/lib/types/user-text.d.ts +24 -2
- package/package.json +8 -6
- package/lib/types/use-copy-feedback.d.ts +0 -14
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment as Fragment$1, cloneElement, createContext, createElement, forwardRef, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
2
|
+
import { Component, Fragment as Fragment$1, cloneElement, createContext, createElement, createRef, forwardRef, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
3
3
|
import css from "./settings-form/SettingsForm.module.css";
|
|
4
4
|
import clsx from "clsx";
|
|
5
5
|
import css$1 from "./Tag.module.css";
|
|
@@ -9,44 +9,48 @@ import css$3 from "./CommandText.module.css";
|
|
|
9
9
|
import css$4 from "./StateDot.module.css";
|
|
10
10
|
import css$5 from "./TextShimmer.module.css";
|
|
11
11
|
import css$6 from "./DisclosureRow.module.css";
|
|
12
|
-
import css$7 from "./
|
|
13
|
-
import css$8 from "./
|
|
14
|
-
import css$9 from "./
|
|
15
|
-
import css$10 from "./
|
|
16
|
-
import css$11 from "./
|
|
17
|
-
import css$12 from "./
|
|
18
|
-
import css$13 from "./
|
|
19
|
-
import css$14 from "./
|
|
12
|
+
import css$7 from "./AnimatedRows.module.css";
|
|
13
|
+
import css$8 from "./RowDragPreview.module.css";
|
|
14
|
+
import css$9 from "./Button.module.css";
|
|
15
|
+
import css$10 from "./Pill.module.css";
|
|
16
|
+
import css$11 from "./SegmentedControl.module.css";
|
|
17
|
+
import css$12 from "./SegmentedTabs.module.css";
|
|
18
|
+
import css$13 from "./Switch.module.css";
|
|
19
|
+
import css$14 from "./Checkbox.module.css";
|
|
20
|
+
import css$15 from "./Input.module.css";
|
|
21
|
+
import css$16 from "./InlineEditor.module.css";
|
|
20
22
|
import { createPortal } from "react-dom";
|
|
21
|
-
import css$
|
|
22
|
-
import css$
|
|
23
|
-
import css$
|
|
24
|
-
import css$
|
|
25
|
-
import css$
|
|
26
|
-
import css$
|
|
27
|
-
import css$
|
|
28
|
-
import css$
|
|
29
|
-
import css$
|
|
30
|
-
import css$
|
|
23
|
+
import css$17 from "./ShortcutKeys.module.css";
|
|
24
|
+
import css$18 from "./MenuSurface.module.css";
|
|
25
|
+
import css$19 from "./Tooltip.module.css";
|
|
26
|
+
import css$20 from "./Menu.module.css";
|
|
27
|
+
import css$21 from "./HoverCard.module.css";
|
|
28
|
+
import css$22 from "./Modal.module.css";
|
|
29
|
+
import css$23 from "./OnboardingSurface.module.css";
|
|
30
|
+
import css$24 from "./RiskConfirmation.module.css";
|
|
31
|
+
import css$25 from "./ConnectionIndicator.module.css";
|
|
32
|
+
import css$26 from "./FileTypeIcon.module.css";
|
|
31
33
|
import { siAliexpress, siApple, siBaidu, siBilibili, siCsdn, siDuckduckgo, siEbay, siFacebook, siGithub, siGitlab, siGoogle, siInstagram, siJuejin, siMdnwebdocs, siNetflix, siNpm, siPypi, siQq, siQuora, siReddit, siSinaweibo, siSpotify, siStackoverflow, siTaobao, siTelegram, siTiktok, siV2ex, siWechat, siWhatsapp, siWikipedia, siX, siYcombinator, siYoutube, siZhihu } from "simple-icons";
|
|
32
34
|
import { pathPartsOf } from "@qilin-agent/util-workspace-path";
|
|
33
|
-
import css$
|
|
34
|
-
import css$
|
|
35
|
-
import css$
|
|
35
|
+
import css$27 from "./PathLabel.module.css";
|
|
36
|
+
import css$28 from "./QuotePreview.module.css";
|
|
37
|
+
import css$29 from "./SecretText.module.css";
|
|
38
|
+
import css$30 from "./user-text.module.css";
|
|
36
39
|
import markdownCss from "./markdown/MarkdownText.module.css";
|
|
37
|
-
import css$
|
|
38
|
-
import css$
|
|
40
|
+
import css$31 from "./Toast.module.css";
|
|
41
|
+
import css$32 from "./UserMessageBubble.module.css";
|
|
42
|
+
import css$33 from "./JsonTree.module.css";
|
|
39
43
|
import Anser from "anser";
|
|
40
|
-
import css$
|
|
44
|
+
import css$34 from "./TerminalBlock.module.css";
|
|
41
45
|
import { createCssVariablesTheme, createHighlighterCoreSync } from "shiki/core";
|
|
42
46
|
import { createJavaScriptRegexEngine, defaultJavaScriptRegexConstructor } from "shiki/engine/javascript";
|
|
43
47
|
import langTs from "@shikijs/langs/typescript";
|
|
44
48
|
import langBash from "@shikijs/langs/shellscript";
|
|
45
49
|
import langJson from "@shikijs/langs/json";
|
|
46
|
-
import css$
|
|
50
|
+
import css$35 from "./ReadBlock.module.css";
|
|
47
51
|
import { structuredPatch } from "diff";
|
|
48
|
-
import css$
|
|
49
|
-
import css$
|
|
52
|
+
import css$36 from "./DiffBlock.module.css";
|
|
53
|
+
import css$37 from "./SearchBlock.module.css";
|
|
50
54
|
import { fromMarkdown } from "mdast-util-from-markdown";
|
|
51
55
|
import { gfmFromMarkdown } from "mdast-util-gfm";
|
|
52
56
|
import { mathFromMarkdown } from "mdast-util-math";
|
|
@@ -57,16 +61,19 @@ import { markdownLineEnding, unicodePunctuation } from "micromark-util-character
|
|
|
57
61
|
import { classifyCharacter } from "micromark-util-classify-character";
|
|
58
62
|
import { codes, constants, types } from "micromark-util-symbol";
|
|
59
63
|
import { factorySpace } from "micromark-factory-space";
|
|
64
|
+
import { decodeString } from "micromark-util-decode-string";
|
|
60
65
|
import { normalizeUri } from "micromark-util-sanitize-uri";
|
|
61
|
-
import css$
|
|
62
|
-
import css$
|
|
66
|
+
import css$38 from "./markdown/CodeBlock.module.css";
|
|
67
|
+
import css$39 from "./markdown/mermaid.module.css";
|
|
63
68
|
import katex from "katex";
|
|
64
|
-
import css$
|
|
65
|
-
import css$
|
|
69
|
+
import css$40 from "./ImageLightbox.module.css";
|
|
70
|
+
import css$41 from "./ImagePreview.module.css";
|
|
66
71
|
import "katex/dist/katex.min.css";
|
|
67
|
-
import css$
|
|
68
|
-
import css$
|
|
69
|
-
import css$
|
|
72
|
+
import css$42 from "./WebBlock.module.css";
|
|
73
|
+
import css$43 from "./DetailDocument.module.css";
|
|
74
|
+
import css$44 from "./markdown/JsonBlock.module.css";
|
|
75
|
+
import { CODE_HIGHLIGHT_EXTENSIONS, languageForPath } from "@qilin-agent/util-code-language";
|
|
76
|
+
import css$45 from "./MenuGroup.module.css";
|
|
70
77
|
//#region lib/types/settings-form/SettingsForm.js
|
|
71
78
|
/**
|
|
72
79
|
* One plugin's settings form as its page on the Plugins page shows it: the
|
|
@@ -281,6 +288,20 @@ const BrowseOutlineArtwork = ({ size = 16, className, strokeWidth }) => jsxs("sv
|
|
|
281
288
|
* @param props - Size, optional CSS class, and inherited stroke width.
|
|
282
289
|
* @returns The decorative SVG artwork.
|
|
283
290
|
*/
|
|
291
|
+
const DragHandleOutlineArtwork = ({ size = 16, className, strokeWidth }) => jsx("svg", {
|
|
292
|
+
width: size,
|
|
293
|
+
height: size,
|
|
294
|
+
className,
|
|
295
|
+
viewBox: "0 0 16 16",
|
|
296
|
+
fill: "none",
|
|
297
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
298
|
+
"aria-hidden": "true",
|
|
299
|
+
strokeWidth,
|
|
300
|
+
children: jsx("path", {
|
|
301
|
+
d: "M4 5h8M4 8h8M4 11h8",
|
|
302
|
+
stroke: "currentColor"
|
|
303
|
+
})
|
|
304
|
+
});
|
|
284
305
|
const FolderCloseArtwork = ({ size = 16, className, strokeWidth }) => jsxs("svg", {
|
|
285
306
|
width: size,
|
|
286
307
|
height: size,
|
|
@@ -1520,6 +1541,40 @@ const IconDownloadOutlineArtwork = ({ size = 16, className, strokeWidth }) => js
|
|
|
1520
1541
|
})
|
|
1521
1542
|
]
|
|
1522
1543
|
});
|
|
1544
|
+
const IconSaveOutlineArtwork = ({ size = 16, className, strokeWidth }) => jsxs("svg", {
|
|
1545
|
+
width: size,
|
|
1546
|
+
height: size,
|
|
1547
|
+
className,
|
|
1548
|
+
viewBox: "0 0 16 16",
|
|
1549
|
+
fill: "none",
|
|
1550
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1551
|
+
"aria-hidden": "true",
|
|
1552
|
+
strokeWidth,
|
|
1553
|
+
children: [
|
|
1554
|
+
jsx("path", {
|
|
1555
|
+
d: "M2.5 3.5C2.5 2.94772 2.94772 2.5 3.5 2.5H10.7929L13.5 5.20711V12.5C13.5 13.0523 13.0523 13.5 12.5 13.5H3.5C2.94772 13.5 2.5 13.0523 2.5 12.5V3.5Z",
|
|
1556
|
+
stroke: "currentColor"
|
|
1557
|
+
}),
|
|
1558
|
+
jsx("path", {
|
|
1559
|
+
d: "M5 2.5V5.5H10.5V2.5",
|
|
1560
|
+
stroke: "currentColor"
|
|
1561
|
+
}),
|
|
1562
|
+
jsx("path", {
|
|
1563
|
+
d: "M5 13.5V9H11V13.5",
|
|
1564
|
+
stroke: "currentColor"
|
|
1565
|
+
})
|
|
1566
|
+
]
|
|
1567
|
+
});
|
|
1568
|
+
/** Regular one-pixel IconSaveOutline artwork. */
|
|
1569
|
+
const IconSaveOutlineRegular = (props) => jsx(IconSaveOutlineArtwork, {
|
|
1570
|
+
...props,
|
|
1571
|
+
strokeWidth: 1
|
|
1572
|
+
});
|
|
1573
|
+
/** Medium IconSaveOutline artwork with a 1.3px stroke. */
|
|
1574
|
+
const IconSaveOutlineMedium = (props) => jsx(IconSaveOutlineArtwork, {
|
|
1575
|
+
...props,
|
|
1576
|
+
strokeWidth: ICON_MEDIUM_STROKE
|
|
1577
|
+
});
|
|
1523
1578
|
/** Regular one-pixel IconDownloadOutline artwork. */
|
|
1524
1579
|
const IconDownloadOutlineRegular = (props) => jsx(IconDownloadOutlineArtwork, {
|
|
1525
1580
|
...props,
|
|
@@ -2252,6 +2307,16 @@ const IconListPenOutlineArtwork = ({ size = 16, className, strokeWidth }) => jsx
|
|
|
2252
2307
|
})
|
|
2253
2308
|
]
|
|
2254
2309
|
});
|
|
2310
|
+
/** Regular one-pixel IconDragHandleOutline artwork. */
|
|
2311
|
+
const IconDragHandleOutlineRegular = (props) => jsx(DragHandleOutlineArtwork, {
|
|
2312
|
+
...props,
|
|
2313
|
+
strokeWidth: 1
|
|
2314
|
+
});
|
|
2315
|
+
/** Medium IconDragHandleOutline artwork with a 1.3px stroke. */
|
|
2316
|
+
const IconDragHandleOutlineMedium = (props) => jsx(DragHandleOutlineArtwork, {
|
|
2317
|
+
...props,
|
|
2318
|
+
strokeWidth: ICON_MEDIUM_STROKE
|
|
2319
|
+
});
|
|
2255
2320
|
/** Regular one-pixel IconListPenOutline artwork. */
|
|
2256
2321
|
const IconListPenOutlineRegular = (props) => jsx(IconListPenOutlineArtwork, {
|
|
2257
2322
|
...props,
|
|
@@ -3883,25 +3948,278 @@ const DisclosureRow = memo(function DisclosureRow({ icon, title, open, expandabl
|
|
|
3883
3948
|
});
|
|
3884
3949
|
});
|
|
3885
3950
|
//#endregion
|
|
3951
|
+
//#region lib/types/motion.js
|
|
3952
|
+
/**
|
|
3953
|
+
* Motion preference for the browser-driven animations in this package.
|
|
3954
|
+
* Animation owners ask before starting movement, so one query decides the
|
|
3955
|
+
* answer for list glides, fades, and marquees alike.
|
|
3956
|
+
*/
|
|
3957
|
+
/**
|
|
3958
|
+
* Whether the platform asked for reduced motion.
|
|
3959
|
+
* A platform without the query (Node, a test DOM that implements no matcher)
|
|
3960
|
+
* has expressed no preference, so motion stays allowed.
|
|
3961
|
+
* @returns true when the reader prefers reduced motion.
|
|
3962
|
+
*/
|
|
3963
|
+
function prefersReducedMotion() {
|
|
3964
|
+
return typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
3965
|
+
}
|
|
3966
|
+
//#endregion
|
|
3967
|
+
//#region lib/types/AnimatedRows.js
|
|
3968
|
+
/** React-commit-driven movement and entry/exit fades for keyed rows. */
|
|
3969
|
+
const ROW_FADE_MS = 100;
|
|
3970
|
+
const ROW_GLIDE_MS = 200;
|
|
3971
|
+
function sameRows(previous, next) {
|
|
3972
|
+
return previous.rowKeys.length === next.rowKeys.length && previous.rowKeys.every((key, index) => key === next.rowKeys[index]);
|
|
3973
|
+
}
|
|
3974
|
+
function intersects(row, viewport) {
|
|
3975
|
+
return row.bottom > viewport.top && row.top < viewport.bottom && row.right > viewport.left && row.left < viewport.right;
|
|
3976
|
+
}
|
|
3977
|
+
/**
|
|
3978
|
+
* Animates keyed rows only when their rendered membership or order changes.
|
|
3979
|
+
* Motion starts after the first pointer or keyboard input inside the mounted list.
|
|
3980
|
+
* The parent supplies a positioned container for the inert exit overlay.
|
|
3981
|
+
*/
|
|
3982
|
+
var AnimatedRows = class extends Component {
|
|
3983
|
+
armed = false;
|
|
3984
|
+
list = createRef();
|
|
3985
|
+
overlay = createRef();
|
|
3986
|
+
movements = /* @__PURE__ */ new Map();
|
|
3987
|
+
exits = /* @__PURE__ */ new Map();
|
|
3988
|
+
getSnapshotBeforeUpdate(previous) {
|
|
3989
|
+
const list = this.list.current;
|
|
3990
|
+
if (!this.armed || sameRows(previous, this.props) || previous.resetKey !== this.props.resetKey || !previous.ready || !this.props.ready || list === null || typeof list.animate !== "function" || prefersReducedMotion()) return null;
|
|
3991
|
+
const viewport = list.getBoundingClientRect();
|
|
3992
|
+
const positions = this.readPositions();
|
|
3993
|
+
const nextKeys = new Set(this.props.rowKeys);
|
|
3994
|
+
const removed = /* @__PURE__ */ new Map();
|
|
3995
|
+
for (const [key, row] of positions) {
|
|
3996
|
+
if (nextKeys.has(key) || !intersects(row.rect, viewport)) continue;
|
|
3997
|
+
const clone = row.element.cloneNode(true);
|
|
3998
|
+
clone.removeAttribute("data-row-key");
|
|
3999
|
+
clone.inert = true;
|
|
4000
|
+
clone.style.paddingInlineStart = getComputedStyle(row.element).paddingInlineStart;
|
|
4001
|
+
removed.set(key, {
|
|
4002
|
+
...row,
|
|
4003
|
+
element: clone
|
|
4004
|
+
});
|
|
4005
|
+
}
|
|
4006
|
+
return {
|
|
4007
|
+
positions,
|
|
4008
|
+
removed
|
|
4009
|
+
};
|
|
4010
|
+
}
|
|
4011
|
+
componentDidUpdate(previous, _state, snapshot) {
|
|
4012
|
+
if (snapshot === null) {
|
|
4013
|
+
if (!sameRows(previous, this.props) || previous.resetKey !== this.props.resetKey || previous.ready !== this.props.ready) this.clear();
|
|
4014
|
+
return;
|
|
4015
|
+
}
|
|
4016
|
+
this.cancelMovements();
|
|
4017
|
+
const list = this.list.current;
|
|
4018
|
+
const overlay = this.overlay.current;
|
|
4019
|
+
const viewport = list.getBoundingClientRect();
|
|
4020
|
+
const origin = overlay.getBoundingClientRect();
|
|
4021
|
+
const positions = this.readPositions();
|
|
4022
|
+
for (const [key, row] of positions) {
|
|
4023
|
+
this.removeExit(key);
|
|
4024
|
+
const previousRow = snapshot.positions.get(key);
|
|
4025
|
+
if (!intersects(row.rect, viewport) && (previousRow === void 0 || !intersects(previousRow.rect, viewport))) continue;
|
|
4026
|
+
if (previousRow === void 0) {
|
|
4027
|
+
this.move(row.element, [{ opacity: 0 }, { opacity: 1 }], ROW_FADE_MS);
|
|
4028
|
+
continue;
|
|
4029
|
+
}
|
|
4030
|
+
const dx = previousRow.rect.left - row.rect.left;
|
|
4031
|
+
const dy = previousRow.rect.top - row.rect.top;
|
|
4032
|
+
if (dx === 0 && dy === 0 && previousRow.opacity === 1) continue;
|
|
4033
|
+
this.move(row.element, [{
|
|
4034
|
+
transform: `translate(${String(dx)}px, ${String(dy)}px)`,
|
|
4035
|
+
opacity: previousRow.opacity
|
|
4036
|
+
}, {
|
|
4037
|
+
transform: "translate(0, 0)",
|
|
4038
|
+
opacity: 1
|
|
4039
|
+
}], ROW_GLIDE_MS);
|
|
4040
|
+
}
|
|
4041
|
+
for (const [key, row] of snapshot.removed) {
|
|
4042
|
+
const { element } = row;
|
|
4043
|
+
this.removeExit(key);
|
|
4044
|
+
Object.assign(element.style, {
|
|
4045
|
+
position: "absolute",
|
|
4046
|
+
margin: "0",
|
|
4047
|
+
transform: "none",
|
|
4048
|
+
boxSizing: "border-box",
|
|
4049
|
+
left: `${String(row.rect.left - origin.left)}px`,
|
|
4050
|
+
top: `${String(row.rect.top - origin.top)}px`,
|
|
4051
|
+
width: `${String(row.rect.width)}px`,
|
|
4052
|
+
height: `${String(row.rect.height)}px`
|
|
4053
|
+
});
|
|
4054
|
+
overlay.append(element);
|
|
4055
|
+
const animation = element.animate([{ opacity: row.opacity }, { opacity: 0 }], {
|
|
4056
|
+
duration: ROW_FADE_MS,
|
|
4057
|
+
easing: "ease-out",
|
|
4058
|
+
fill: "forwards"
|
|
4059
|
+
});
|
|
4060
|
+
this.exits.set(key, {
|
|
4061
|
+
element,
|
|
4062
|
+
animation
|
|
4063
|
+
});
|
|
4064
|
+
animation.onfinish = () => {
|
|
4065
|
+
this.removeExit(key);
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
4068
|
+
}
|
|
4069
|
+
componentWillUnmount() {
|
|
4070
|
+
this.clear();
|
|
4071
|
+
}
|
|
4072
|
+
readPositions() {
|
|
4073
|
+
const rows = this.list.current.querySelectorAll("[data-row-key]");
|
|
4074
|
+
return new Map(Array.from(rows, (element) => [element.dataset.rowKey, {
|
|
4075
|
+
element,
|
|
4076
|
+
rect: element.getBoundingClientRect(),
|
|
4077
|
+
opacity: this.movements.has(element) ? Number(getComputedStyle(element).opacity) : 1
|
|
4078
|
+
}]));
|
|
4079
|
+
}
|
|
4080
|
+
move(element, keyframes, duration) {
|
|
4081
|
+
const animation = element.animate(keyframes, {
|
|
4082
|
+
duration,
|
|
4083
|
+
easing: "ease-out"
|
|
4084
|
+
});
|
|
4085
|
+
this.movements.set(element, animation);
|
|
4086
|
+
animation.onfinish = () => {
|
|
4087
|
+
this.movements.delete(element);
|
|
4088
|
+
animation.cancel();
|
|
4089
|
+
};
|
|
4090
|
+
}
|
|
4091
|
+
cancelMovements() {
|
|
4092
|
+
for (const animation of this.movements.values()) {
|
|
4093
|
+
animation.onfinish = null;
|
|
4094
|
+
animation.cancel();
|
|
4095
|
+
}
|
|
4096
|
+
this.movements.clear();
|
|
4097
|
+
}
|
|
4098
|
+
removeExit(key) {
|
|
4099
|
+
const exit = this.exits.get(key);
|
|
4100
|
+
if (exit === void 0) return;
|
|
4101
|
+
exit.animation.onfinish = null;
|
|
4102
|
+
exit.animation.cancel();
|
|
4103
|
+
exit.element.remove();
|
|
4104
|
+
this.exits.delete(key);
|
|
4105
|
+
}
|
|
4106
|
+
clear() {
|
|
4107
|
+
this.cancelMovements();
|
|
4108
|
+
for (const key of this.exits.keys()) this.removeExit(key);
|
|
4109
|
+
}
|
|
4110
|
+
render() {
|
|
4111
|
+
return jsxs(Fragment, { children: [jsx("div", {
|
|
4112
|
+
ref: this.list,
|
|
4113
|
+
className: this.props.className,
|
|
4114
|
+
role: this.props.role,
|
|
4115
|
+
"aria-label": this.props.label,
|
|
4116
|
+
onPointerDownCapture: () => {
|
|
4117
|
+
this.armed = true;
|
|
4118
|
+
},
|
|
4119
|
+
onKeyDownCapture: () => {
|
|
4120
|
+
this.armed = true;
|
|
4121
|
+
},
|
|
4122
|
+
children: this.props.children
|
|
4123
|
+
}), jsx("div", {
|
|
4124
|
+
ref: this.overlay,
|
|
4125
|
+
className: css$7.exits,
|
|
4126
|
+
"aria-hidden": "true"
|
|
4127
|
+
})] });
|
|
4128
|
+
}
|
|
4129
|
+
};
|
|
4130
|
+
//#endregion
|
|
4131
|
+
//#region lib/types/useRowDragPreview.js
|
|
4132
|
+
/** Offscreen row snapshots for native browser dragging. */
|
|
4133
|
+
/**
|
|
4134
|
+
* Own a row's inert drag preview until drag end, replacement, or unmount.
|
|
4135
|
+
* The offscreen copy keeps WebKit snapshots separate from overlapping panels.
|
|
4136
|
+
* The wrapper copies only explicitly requested computed custom properties.
|
|
4137
|
+
* Its cloned slot DOM remains in document.body for that lifetime; query live rows
|
|
4138
|
+
* within their list to exclude duplicate text and element IDs.
|
|
4139
|
+
* @param className - preview background matching the row's surface; WebKit fills
|
|
4140
|
+
* transparent snapshot pixels white, including rounded corners.
|
|
4141
|
+
* @param inheritedProperties - custom-property names whose computed values the preview needs.
|
|
4142
|
+
* Omit to copy none. Keep the list reference stable; replacing it clears the active preview.
|
|
4143
|
+
* @returns preview callbacks; cleanup reports whether this row started a drag,
|
|
4144
|
+
* even when its preview failed or the row became non-draggable before drag end.
|
|
4145
|
+
* Start accepts the source row when a child handle owns the drag event.
|
|
4146
|
+
*/
|
|
4147
|
+
function useRowDragPreview(className, inheritedProperties) {
|
|
4148
|
+
const current = useRef(null);
|
|
4149
|
+
const dragging = useRef(false);
|
|
4150
|
+
const preview = useMemo(() => {
|
|
4151
|
+
const clear = () => {
|
|
4152
|
+
const wasDragging = dragging.current;
|
|
4153
|
+
dragging.current = false;
|
|
4154
|
+
current.current?.remove();
|
|
4155
|
+
current.current = null;
|
|
4156
|
+
return wasDragging;
|
|
4157
|
+
};
|
|
4158
|
+
return {
|
|
4159
|
+
clear,
|
|
4160
|
+
start: (event, row = event.currentTarget) => {
|
|
4161
|
+
clear();
|
|
4162
|
+
dragging.current = true;
|
|
4163
|
+
const rect = row.getBoundingClientRect();
|
|
4164
|
+
const style = getComputedStyle(row);
|
|
4165
|
+
const copy = row.cloneNode(true);
|
|
4166
|
+
copy.removeAttribute("data-row-key");
|
|
4167
|
+
copy.tabIndex = -1;
|
|
4168
|
+
for (const control of copy.querySelectorAll("button, a[href], input, select, textarea, [tabindex], [contenteditable]")) control.tabIndex = -1;
|
|
4169
|
+
copy.style.color = style.color;
|
|
4170
|
+
copy.style.paddingInlineStart = style.paddingInlineStart;
|
|
4171
|
+
const image = row.ownerDocument.createElement("div");
|
|
4172
|
+
image.className = clsx(css$8.preview, className);
|
|
4173
|
+
image.inert = true;
|
|
4174
|
+
image.setAttribute("aria-hidden", "true");
|
|
4175
|
+
inheritedProperties?.forEach((property) => {
|
|
4176
|
+
image.style.setProperty(property, style.getPropertyValue(property));
|
|
4177
|
+
});
|
|
4178
|
+
Object.assign(image.style, {
|
|
4179
|
+
left: `${String(-rect.width)}px`,
|
|
4180
|
+
width: `${String(rect.width)}px`,
|
|
4181
|
+
height: `${String(rect.height)}px`,
|
|
4182
|
+
font: style.font
|
|
4183
|
+
});
|
|
4184
|
+
image.append(copy);
|
|
4185
|
+
row.ownerDocument.body.append(image);
|
|
4186
|
+
current.current = image;
|
|
4187
|
+
try {
|
|
4188
|
+
event.dataTransfer.setDragImage(image, event.clientX - rect.left, event.clientY - rect.top);
|
|
4189
|
+
} catch (_error) {
|
|
4190
|
+
image.remove();
|
|
4191
|
+
current.current = null;
|
|
4192
|
+
}
|
|
4193
|
+
}
|
|
4194
|
+
};
|
|
4195
|
+
}, [className, inheritedProperties]);
|
|
4196
|
+
useEffect(() => () => {
|
|
4197
|
+
preview.clear();
|
|
4198
|
+
}, [preview]);
|
|
4199
|
+
return preview;
|
|
4200
|
+
}
|
|
4201
|
+
//#endregion
|
|
3886
4202
|
//#region lib/types/Button.js
|
|
3887
4203
|
/**
|
|
3888
4204
|
* Render a button.
|
|
3889
4205
|
* @param props.variant - visual family (default 'ghost').
|
|
3890
4206
|
* @param props.size - 'md' 36px capsule (figma Button) or 'sm' 28px compact.
|
|
3891
4207
|
* @param props.icon - optional leading 16px icon node.
|
|
4208
|
+
* @param ref - native button for focus management and overlay anchors.
|
|
3892
4209
|
* @returns the button element; native button attributes pass through.
|
|
3893
4210
|
*/
|
|
3894
|
-
function Button({ variant = "ghost", size = "md", icon, className, children, ...rest }) {
|
|
4211
|
+
const Button = forwardRef(function Button({ variant = "ghost", size = "md", icon, className, children, ...rest }, ref) {
|
|
3895
4212
|
return jsxs("button", {
|
|
4213
|
+
ref,
|
|
3896
4214
|
type: "button",
|
|
3897
|
-
className: clsx(css$
|
|
4215
|
+
className: clsx(css$9.button, css$9[variant], css$9[size], className),
|
|
3898
4216
|
...rest,
|
|
3899
4217
|
children: [icon != null && jsx("span", {
|
|
3900
|
-
className: css$
|
|
4218
|
+
className: css$9.icon,
|
|
3901
4219
|
children: icon
|
|
3902
4220
|
}), children]
|
|
3903
4221
|
});
|
|
3904
|
-
}
|
|
4222
|
+
});
|
|
3905
4223
|
//#endregion
|
|
3906
4224
|
//#region lib/types/Pill.js
|
|
3907
4225
|
/**
|
|
@@ -3912,12 +4230,12 @@ function Button({ variant = "ghost", size = "md", icon, className, children, ...
|
|
|
3912
4230
|
*/
|
|
3913
4231
|
function Pill({ active = false, className, children, onClick, ...rest }) {
|
|
3914
4232
|
if (!onClick) return jsx("span", {
|
|
3915
|
-
className: clsx(css$
|
|
4233
|
+
className: clsx(css$10.pill, active && css$10.active, className),
|
|
3916
4234
|
children
|
|
3917
4235
|
});
|
|
3918
4236
|
return jsx("button", {
|
|
3919
4237
|
type: "button",
|
|
3920
|
-
className: clsx(css$
|
|
4238
|
+
className: clsx(css$10.pill, css$10.interactive, active && css$10.active, className),
|
|
3921
4239
|
onClick,
|
|
3922
4240
|
...rest,
|
|
3923
4241
|
children
|
|
@@ -3982,11 +4300,11 @@ function SegmentedControl({ id, value, options, onChange, label, disabled = fals
|
|
|
3982
4300
|
ref: list,
|
|
3983
4301
|
role: "tablist",
|
|
3984
4302
|
"aria-label": label,
|
|
3985
|
-
className: clsx(css$
|
|
4303
|
+
className: clsx(css$11.control, className),
|
|
3986
4304
|
style: indicator,
|
|
3987
4305
|
children: [jsx("span", {
|
|
3988
4306
|
"aria-hidden": "true",
|
|
3989
|
-
className: css$
|
|
4307
|
+
className: css$11.indicator
|
|
3990
4308
|
}), options.map((option) => {
|
|
3991
4309
|
const active = option.value === value;
|
|
3992
4310
|
return jsx("button", {
|
|
@@ -3998,7 +4316,7 @@ function SegmentedControl({ id, value, options, onChange, label, disabled = fals
|
|
|
3998
4316
|
tabIndex: active ? 0 : -1,
|
|
3999
4317
|
disabled: disabled || option.disabled === true,
|
|
4000
4318
|
title: option.title,
|
|
4001
|
-
className: css$
|
|
4319
|
+
className: css$11.tab,
|
|
4002
4320
|
onClick: () => {
|
|
4003
4321
|
if (!active) onChange(option.value);
|
|
4004
4322
|
},
|
|
@@ -4018,9 +4336,10 @@ function SegmentedControl({ id, value, options, onChange, label, disabled = fals
|
|
|
4018
4336
|
* Keyboard selection also moves focus; only the selected tab is a tab stop.
|
|
4019
4337
|
* @param props.label - localized accessible name for the tab list.
|
|
4020
4338
|
* @param props.className - layout placement; panels remain caller-owned.
|
|
4339
|
+
* @param props.size - `sm` draws 30px tabs with a 2px inset (34px tall); the default `md` draws 34px tabs with a 4px inset (42px tall).
|
|
4021
4340
|
* @returns the tab list, without its panels.
|
|
4022
4341
|
*/
|
|
4023
|
-
function SegmentedTabs({ items, value, onChange, label, className }) {
|
|
4342
|
+
function SegmentedTabs({ items, value, onChange, label, className, size = "md" }) {
|
|
4024
4343
|
const selectedIndex = items.findIndex((item) => item.value === value);
|
|
4025
4344
|
const onKeyDown = (event, index) => {
|
|
4026
4345
|
let next;
|
|
@@ -4050,20 +4369,21 @@ function SegmentedTabs({ items, value, onChange, label, className }) {
|
|
|
4050
4369
|
};
|
|
4051
4370
|
return jsxs("div", {
|
|
4052
4371
|
role: "tablist",
|
|
4372
|
+
"data-size": size,
|
|
4053
4373
|
"aria-label": label,
|
|
4054
|
-
className: clsx(css$
|
|
4374
|
+
className: clsx(css$12.tabs, className),
|
|
4055
4375
|
style: { gridTemplateColumns: `repeat(${items.length}, minmax(0, 1fr))` },
|
|
4056
4376
|
children: [jsx("span", {
|
|
4057
|
-
className: css$
|
|
4377
|
+
className: css$12.indicator,
|
|
4058
4378
|
"aria-hidden": "true",
|
|
4059
4379
|
style: {
|
|
4060
|
-
width: `calc((100% -
|
|
4380
|
+
width: `calc((100% - 2 * var(--segmented-tabs-inset)) / ${items.length})`,
|
|
4061
4381
|
transform: `translateX(${selectedIndex * 100}%)`
|
|
4062
4382
|
}
|
|
4063
4383
|
}), items.map((item, index) => jsx(Pill, {
|
|
4064
4384
|
id: item.id,
|
|
4065
4385
|
role: "tab",
|
|
4066
|
-
className: css$
|
|
4386
|
+
className: css$12.tab,
|
|
4067
4387
|
"aria-selected": value === item.value,
|
|
4068
4388
|
"aria-controls": item.panelId,
|
|
4069
4389
|
tabIndex: value === item.value ? 0 : -1,
|
|
@@ -4297,11 +4617,11 @@ function Switch({ checked, onChange, label, disabled = false, title, className }
|
|
|
4297
4617
|
"aria-label": label,
|
|
4298
4618
|
title,
|
|
4299
4619
|
disabled,
|
|
4300
|
-
className: clsx(css$
|
|
4620
|
+
className: clsx(css$13.switch, className),
|
|
4301
4621
|
onClick: () => {
|
|
4302
4622
|
onChange(!checked);
|
|
4303
4623
|
},
|
|
4304
|
-
children: jsx("span", { className: css$
|
|
4624
|
+
children: jsx("span", { className: css$13.thumb })
|
|
4305
4625
|
});
|
|
4306
4626
|
}
|
|
4307
4627
|
//#endregion
|
|
@@ -4319,7 +4639,7 @@ function Switch({ checked, onChange, label, disabled = false, title, className }
|
|
|
4319
4639
|
*/
|
|
4320
4640
|
function Checkbox({ checked, onChange, label, disabled = false, title, className }) {
|
|
4321
4641
|
return jsxs("label", {
|
|
4322
|
-
className: clsx(css$
|
|
4642
|
+
className: clsx(css$14.checkbox, className),
|
|
4323
4643
|
title,
|
|
4324
4644
|
children: [jsx("input", {
|
|
4325
4645
|
type: "checkbox",
|
|
@@ -4341,13 +4661,13 @@ function Checkbox({ checked, onChange, label, disabled = false, title, className
|
|
|
4341
4661
|
*/
|
|
4342
4662
|
const Input = forwardRef(function Input({ icon, className, ...rest }, ref) {
|
|
4343
4663
|
return jsxs("span", {
|
|
4344
|
-
className: clsx(css$
|
|
4664
|
+
className: clsx(css$15.wrap, className),
|
|
4345
4665
|
children: [icon != null && jsx("span", {
|
|
4346
|
-
className: css$
|
|
4666
|
+
className: css$15.icon,
|
|
4347
4667
|
children: icon
|
|
4348
4668
|
}), jsx("input", {
|
|
4349
4669
|
ref,
|
|
4350
|
-
className: css$
|
|
4670
|
+
className: css$15.input,
|
|
4351
4671
|
...rest
|
|
4352
4672
|
})]
|
|
4353
4673
|
});
|
|
@@ -4397,7 +4717,7 @@ function InlineEditor({ value, label, onChange, onSave, onCancel, className }) {
|
|
|
4397
4717
|
ref,
|
|
4398
4718
|
autoFocus: true,
|
|
4399
4719
|
rows: 1,
|
|
4400
|
-
className: clsx(css$
|
|
4720
|
+
className: clsx(css$16.editor, className),
|
|
4401
4721
|
"aria-label": label,
|
|
4402
4722
|
value,
|
|
4403
4723
|
onChange: (event) => {
|
|
@@ -4412,13 +4732,150 @@ function InlineEditor({ value, label, onChange, onSave, onCancel, className }) {
|
|
|
4412
4732
|
/**
|
|
4413
4733
|
* Resolve the top margin an overlay keeps from the viewport edge.
|
|
4414
4734
|
* @param min - the overlay's own viewport margin in px, used as the floor.
|
|
4415
|
-
* @
|
|
4416
|
-
|
|
4417
|
-
|
|
4735
|
+
* @param anchor - the overlay's anchor rect in viewport coordinates. While the
|
|
4736
|
+
* anchor's top edge is visible, the margin never exceeds `anchor.bottom + gap`,
|
|
4737
|
+
* so an overlay opened from a control inside the top strip stays next to it.
|
|
4738
|
+
* @param gap - the distance the overlay keeps below `anchor`.
|
|
4739
|
+
* @returns the larger of `min` and the frame clearance plus 20px (20px in
|
|
4740
|
+
* fullscreen), capped by a visible `anchor`, but never below `min`.
|
|
4741
|
+
*/
|
|
4742
|
+
function overlayTopMargin(min, anchor, gap = 0) {
|
|
4418
4743
|
const root = document.documentElement;
|
|
4419
4744
|
const clearance = Number.parseFloat(getComputedStyle(root).getPropertyValue("--dsh-frame-top-clearance"));
|
|
4420
4745
|
if (Number.isNaN(clearance)) return min;
|
|
4421
|
-
|
|
4746
|
+
const cap = anchor !== void 0 && anchor.top >= 0 ? anchor.bottom + gap : Number.POSITIVE_INFINITY;
|
|
4747
|
+
return Math.max(min, Math.min((root.hasAttribute("data-fullscreen") ? 0 : clearance) + 20, cap));
|
|
4748
|
+
}
|
|
4749
|
+
//#endregion
|
|
4750
|
+
//#region lib/types/useAnchoredPosition.js
|
|
4751
|
+
/**
|
|
4752
|
+
* Synchronous placement for body-portaled panels fixed to the visual viewport.
|
|
4753
|
+
*
|
|
4754
|
+
* A panel is placed from its anchor's current client rect, and that fact stops
|
|
4755
|
+
* being true on scroll, resize, browser zoom, the on-screen keyboard, or an
|
|
4756
|
+
* ancestor transform. This owns the whole placement concern: it measures the
|
|
4757
|
+
* anchor, keeps the panel inside the visible viewport, publishes the panel's
|
|
4758
|
+
* size limits as CSS variables, and tracks the anchor while the panel is open.
|
|
4759
|
+
* The placement runs in a layout effect before paint, so the first open frame
|
|
4760
|
+
* already carries the final coordinates.
|
|
4761
|
+
* @module @qilin-agent/client-ui-primitives/useAnchoredPosition
|
|
4762
|
+
*/
|
|
4763
|
+
/**
|
|
4764
|
+
* Keep a fixed panel aligned and constrained to the visible viewport.
|
|
4765
|
+
* Panel CSS consumes `--qilin-anchored-max-width` and
|
|
4766
|
+
* `--qilin-anchored-max-height` for its size limits; the legacy
|
|
4767
|
+
* `--dsh-anchored-*` names are mirrored while open, so sheets that still read
|
|
4768
|
+
* the earlier contract keep their caps.
|
|
4769
|
+
* @param options - Open state, element refs, placement, and optional host rect or content change key.
|
|
4770
|
+
* @returns Fixed left/top coordinates, or null while closed or awaiting an anchor; the first measurement completes before paint.
|
|
4771
|
+
*/
|
|
4772
|
+
function useAnchoredPosition(options) {
|
|
4773
|
+
const { open, anchorRef, panelRef, side = "bottom", align = "start", gap, margin, getAnchorRect, layoutKey, minWidthFromAnchor = false, frameClearance = true } = options;
|
|
4774
|
+
const [position, setPosition] = useState(null);
|
|
4775
|
+
useLayoutEffect(() => {
|
|
4776
|
+
if (!open) {
|
|
4777
|
+
setPosition(null);
|
|
4778
|
+
return;
|
|
4779
|
+
}
|
|
4780
|
+
const anchor = anchorRef.current;
|
|
4781
|
+
const panel = panelRef.current;
|
|
4782
|
+
if (anchor === null || panel === null) return;
|
|
4783
|
+
const win = anchor.ownerDocument.defaultView;
|
|
4784
|
+
if (win === null) return;
|
|
4785
|
+
const viewport = win.visualViewport;
|
|
4786
|
+
const visualOrigin = typeof win.CSS !== "undefined" && win.CSS.supports("-webkit-backdrop-filter", "none");
|
|
4787
|
+
const previous = [
|
|
4788
|
+
"--qilin-anchored-max-width",
|
|
4789
|
+
"--qilin-anchored-max-height",
|
|
4790
|
+
"--dsh-anchored-max-width",
|
|
4791
|
+
"--dsh-anchored-max-height",
|
|
4792
|
+
...minWidthFromAnchor ? ["min-width"] : []
|
|
4793
|
+
].map((name) => [
|
|
4794
|
+
name,
|
|
4795
|
+
panel.style.getPropertyValue(name),
|
|
4796
|
+
panel.style.getPropertyPriority(name)
|
|
4797
|
+
]);
|
|
4798
|
+
let active = true;
|
|
4799
|
+
let lastRect = null;
|
|
4800
|
+
const readRect = () => getAnchorRect === void 0 ? anchor.getBoundingClientRect() : getAnchorRect();
|
|
4801
|
+
const place = (rect) => {
|
|
4802
|
+
lastRect = rect === null ? null : {
|
|
4803
|
+
x: rect.x,
|
|
4804
|
+
y: rect.y,
|
|
4805
|
+
width: rect.width,
|
|
4806
|
+
height: rect.height
|
|
4807
|
+
};
|
|
4808
|
+
let next = null;
|
|
4809
|
+
if (rect !== null) {
|
|
4810
|
+
const x = viewport?.offsetLeft ?? 0;
|
|
4811
|
+
const y = viewport?.offsetTop ?? 0;
|
|
4812
|
+
const width = viewport?.width ?? (anchor.ownerDocument.documentElement.clientWidth || win.innerWidth);
|
|
4813
|
+
const height = viewport?.height ?? (anchor.ownerDocument.documentElement.clientHeight || win.innerHeight);
|
|
4814
|
+
const topMargin = frameClearance ? overlayTopMargin(margin, rect, gap) : margin;
|
|
4815
|
+
const maxWidth = Math.max(0, width - margin * 2);
|
|
4816
|
+
const maxHeight = Math.max(0, height - topMargin - margin);
|
|
4817
|
+
panel.style.setProperty("--qilin-anchored-max-width", `${maxWidth}px`);
|
|
4818
|
+
panel.style.setProperty("--qilin-anchored-max-height", `${maxHeight}px`);
|
|
4819
|
+
panel.style.setProperty("--dsh-anchored-max-width", `${maxWidth}px`);
|
|
4820
|
+
panel.style.setProperty("--dsh-anchored-max-height", `${maxHeight}px`);
|
|
4821
|
+
if (minWidthFromAnchor) panel.style.minWidth = `${Math.min(rect.width, maxWidth)}px`;
|
|
4822
|
+
const panelWidth = panel.offsetWidth;
|
|
4823
|
+
const panelHeight = panel.offsetHeight;
|
|
4824
|
+
const offsetX = visualOrigin ? x : 0;
|
|
4825
|
+
const offsetY = visualOrigin ? y : 0;
|
|
4826
|
+
const left = offsetX + (side === "right" ? rect.right + gap : align === "end" ? rect.right - panelWidth : rect.left);
|
|
4827
|
+
const top = offsetY + (side === "right" ? rect.top : side === "top" ? rect.top - gap - panelHeight : rect.bottom + gap);
|
|
4828
|
+
next = {
|
|
4829
|
+
left: Math.max(x + margin, Math.min(left, x + width - margin - panelWidth)),
|
|
4830
|
+
top: Math.max(y + topMargin, Math.min(top, y + height - margin - panelHeight))
|
|
4831
|
+
};
|
|
4832
|
+
}
|
|
4833
|
+
setPosition((current) => current?.left === next?.left && current?.top === next?.top ? current : next);
|
|
4834
|
+
};
|
|
4835
|
+
const update = () => {
|
|
4836
|
+
if (!active) return;
|
|
4837
|
+
place(readRect());
|
|
4838
|
+
};
|
|
4839
|
+
update();
|
|
4840
|
+
win.addEventListener("scroll", update, true);
|
|
4841
|
+
win.addEventListener("resize", update);
|
|
4842
|
+
viewport?.addEventListener("scroll", update);
|
|
4843
|
+
viewport?.addEventListener("resize", update);
|
|
4844
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(update);
|
|
4845
|
+
observer?.observe(anchor);
|
|
4846
|
+
observer?.observe(panel);
|
|
4847
|
+
let frame = 0;
|
|
4848
|
+
const track = () => {
|
|
4849
|
+
if (!active) return;
|
|
4850
|
+
const rect = readRect();
|
|
4851
|
+
if (rect?.x !== lastRect?.x || rect?.y !== lastRect?.y || rect?.width !== lastRect?.width || rect?.height !== lastRect?.height) place(rect);
|
|
4852
|
+
frame = win.requestAnimationFrame(track);
|
|
4853
|
+
};
|
|
4854
|
+
frame = win.requestAnimationFrame(track);
|
|
4855
|
+
return () => {
|
|
4856
|
+
active = false;
|
|
4857
|
+
win.cancelAnimationFrame(frame);
|
|
4858
|
+
observer?.disconnect();
|
|
4859
|
+
win.removeEventListener("scroll", update, true);
|
|
4860
|
+
win.removeEventListener("resize", update);
|
|
4861
|
+
viewport?.removeEventListener("scroll", update);
|
|
4862
|
+
viewport?.removeEventListener("resize", update);
|
|
4863
|
+
for (const [name, value, priority] of previous) panel.style.setProperty(name, value, priority);
|
|
4864
|
+
};
|
|
4865
|
+
}, [
|
|
4866
|
+
open,
|
|
4867
|
+
anchorRef,
|
|
4868
|
+
panelRef,
|
|
4869
|
+
side,
|
|
4870
|
+
align,
|
|
4871
|
+
gap,
|
|
4872
|
+
margin,
|
|
4873
|
+
getAnchorRect,
|
|
4874
|
+
layoutKey,
|
|
4875
|
+
minWidthFromAnchor,
|
|
4876
|
+
frameClearance
|
|
4877
|
+
]);
|
|
4878
|
+
return position;
|
|
4422
4879
|
}
|
|
4423
4880
|
/**
|
|
4424
4881
|
* Delay a pointer-dismissed popup's close so the pointer can cross the gap
|
|
@@ -4494,7 +4951,7 @@ function observeComposition(document) {
|
|
|
4494
4951
|
}
|
|
4495
4952
|
//#endregion
|
|
4496
4953
|
//#region lib/types/focus.js
|
|
4497
|
-
/** Focus presentation for automatic entry and restoration. */
|
|
4954
|
+
/** Focus presentation for automatic entry and restoration, plus device input shape. */
|
|
4498
4955
|
const releases = /* @__PURE__ */ new WeakMap();
|
|
4499
4956
|
const navigationKeys = new Set([
|
|
4500
4957
|
"Tab",
|
|
@@ -4530,6 +4987,21 @@ function focusWithoutRing(element, options) {
|
|
|
4530
4987
|
element.focus(options);
|
|
4531
4988
|
if (!element.matches(":focus")) release();
|
|
4532
4989
|
}
|
|
4990
|
+
/** Cached answer for mobile-device detection: one browser viewport has one answer. */
|
|
4991
|
+
let mobileDevice;
|
|
4992
|
+
/**
|
|
4993
|
+
* Whether this browser runs on a touch-first mobile device.
|
|
4994
|
+
*
|
|
4995
|
+
* The answer is cached, so a caller must not expect it to follow a viewport
|
|
4996
|
+
* resize. It drives input-shape choices only; layout reacts to container width.
|
|
4997
|
+
* @returns `true` on a mobile browser, `false` on a desktop one.
|
|
4998
|
+
*/
|
|
4999
|
+
function isMobileDevice() {
|
|
5000
|
+
if (mobileDevice !== void 0) return mobileDevice;
|
|
5001
|
+
const { userAgent, platform, maxTouchPoints } = navigator;
|
|
5002
|
+
mobileDevice = /Android|iPhone|iPad|iPod/i.test(userAgent) || platform === "MacIntel" && maxTouchPoints > 1;
|
|
5003
|
+
return mobileDevice;
|
|
5004
|
+
}
|
|
4533
5005
|
//#endregion
|
|
4534
5006
|
//#region lib/types/useModalLayer.js
|
|
4535
5007
|
/** Shared modal keyboard ownership and focus lifetime. */
|
|
@@ -4629,9 +5101,9 @@ function useModalLayer(dialog, open, onClose) {
|
|
|
4629
5101
|
*/
|
|
4630
5102
|
function ShortcutKeys({ keys, variant = "plain", className }) {
|
|
4631
5103
|
return jsx("span", {
|
|
4632
|
-
className: clsx(css$
|
|
5104
|
+
className: clsx(css$17.keys, variant === "tooltip" && css$17.tooltip, variant === "tooltip" && keys.includes("+") && css$17.joined, className),
|
|
4633
5105
|
children: keys.map((key, index) => jsx("kbd", {
|
|
4634
|
-
className: key === "+" ? css$
|
|
5106
|
+
className: key === "+" ? css$17.separator : css$17.key,
|
|
4635
5107
|
children: key
|
|
4636
5108
|
}, index))
|
|
4637
5109
|
});
|
|
@@ -4639,6 +5111,13 @@ function ShortcutKeys({ keys, variant = "plain", className }) {
|
|
|
4639
5111
|
//#endregion
|
|
4640
5112
|
//#region lib/types/MenuSurface.js
|
|
4641
5113
|
/** Shared menu material and the macOS backing that lets Chromium blur transparent windows. */
|
|
5114
|
+
/** Context-menu transition durations in milliseconds, shared with its lifecycle owner. */
|
|
5115
|
+
const MENU_TRANSITION_MS = Object.freeze({
|
|
5116
|
+
enter: 40,
|
|
5117
|
+
dismiss: 80,
|
|
5118
|
+
replace: 10,
|
|
5119
|
+
prepare: 20
|
|
5120
|
+
});
|
|
4642
5121
|
/**
|
|
4643
5122
|
* Paint a menu and, on macOS, an opaque backing behind the page content within its bounds.
|
|
4644
5123
|
* CSS anchors keep each backing aligned during placement, resizing, and nested-menu movement.
|
|
@@ -4646,31 +5125,38 @@ function ShortcutKeys({ keys, variant = "plain", className }) {
|
|
|
4646
5125
|
* @param ref - The visible menu div, excluding the non-interactive backing.
|
|
4647
5126
|
* @returns Menu content plus a backing portal removed with the menu.
|
|
4648
5127
|
*/
|
|
4649
|
-
const MenuSurface = forwardRef(function MenuSurface({ compact = false, className, style, children, ...props }, ref) {
|
|
5128
|
+
const MenuSurface = forwardRef(function MenuSurface({ compact = false, phase, className, style, children, ...props }, ref) {
|
|
4650
5129
|
const id = useId();
|
|
4651
5130
|
const backingRef = useRef(null);
|
|
4652
5131
|
useLayoutEffect(() => {
|
|
4653
5132
|
document.body.appendChild(backingRef.current);
|
|
4654
5133
|
}, []);
|
|
4655
|
-
const anchorStyle = {
|
|
5134
|
+
const anchorStyle = {
|
|
5135
|
+
"--dsh-menu-anchor": `--dsh-menu-${id.replaceAll(":", "")}`,
|
|
5136
|
+
"--dsh-menu-enter": `${MENU_TRANSITION_MS.enter}ms`,
|
|
5137
|
+
"--dsh-menu-dismiss": `${MENU_TRANSITION_MS.dismiss}ms`,
|
|
5138
|
+
"--dsh-menu-replace": `${MENU_TRANSITION_MS.replace}ms`
|
|
5139
|
+
};
|
|
4656
5140
|
return jsxs(Fragment, { children: [jsxs("div", {
|
|
4657
5141
|
...props,
|
|
4658
5142
|
ref,
|
|
4659
5143
|
"data-menu-material": "translucent",
|
|
4660
|
-
|
|
5144
|
+
"data-menu-phase": phase,
|
|
5145
|
+
className: clsx(css$18.surface, compact && css$18.compact, className),
|
|
4661
5146
|
style: {
|
|
4662
5147
|
...style,
|
|
4663
5148
|
...anchorStyle
|
|
4664
5149
|
},
|
|
4665
5150
|
children: [jsx("div", {
|
|
4666
5151
|
"aria-hidden": "true",
|
|
4667
|
-
className: css$
|
|
5152
|
+
className: css$18.material
|
|
4668
5153
|
}), children]
|
|
4669
5154
|
}), createPortal(jsx("div", {
|
|
4670
5155
|
ref: backingRef,
|
|
4671
5156
|
"aria-hidden": "true",
|
|
4672
5157
|
"data-menu-backing": "",
|
|
4673
|
-
|
|
5158
|
+
"data-menu-phase": phase,
|
|
5159
|
+
className: clsx(css$18.backing, compact && css$18.compact),
|
|
4674
5160
|
style: {
|
|
4675
5161
|
...anchorStyle,
|
|
4676
5162
|
visibility: style?.visibility
|
|
@@ -4678,587 +5164,74 @@ const MenuSurface = forwardRef(function MenuSurface({ compact = false, className
|
|
|
4678
5164
|
}), document.body)] });
|
|
4679
5165
|
});
|
|
4680
5166
|
//#endregion
|
|
4681
|
-
//#region lib/types/
|
|
5167
|
+
//#region lib/types/tooltip-placement.js
|
|
5168
|
+
const opposite = {
|
|
5169
|
+
right: "left",
|
|
5170
|
+
left: "right",
|
|
5171
|
+
top: "bottom",
|
|
5172
|
+
bottom: "top"
|
|
5173
|
+
};
|
|
4682
5174
|
/**
|
|
4683
|
-
*
|
|
4684
|
-
*
|
|
4685
|
-
*
|
|
4686
|
-
* without any shared state. Closing the menu stays the owner's decision, as
|
|
4687
|
-
* it is for data rows.
|
|
4688
|
-
* @param props.children - visible row label.
|
|
4689
|
-
* @param props.shortcut - effective key labels and accessible combination.
|
|
4690
|
-
* @param props.icon - optional leading icon.
|
|
4691
|
-
* @param props.current - whether the row identifies the currently displayed page or view.
|
|
4692
|
-
* @param props.disabled - whether the row cannot be activated.
|
|
4693
|
-
* @param props.danger - whether to use the destructive row colors.
|
|
4694
|
-
* @param props.separatorBefore - whether this row starts a new group (hairline above it).
|
|
4695
|
-
* @param props.onSelect - row activation callback.
|
|
4696
|
-
* @returns one menu-item row.
|
|
5175
|
+
* Prefer the requested side, its opposite, then perpendicular sides without covering the anchor.
|
|
5176
|
+
* @param input - Observed dimensions, anchor edges, viewport and requested placement; horizontal gaps remain 10px.
|
|
5177
|
+
* @returns Border-box coordinates and the selected side; if no side fits, a viewport-bounded fallback.
|
|
4697
5178
|
*/
|
|
4698
|
-
function
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
}
|
|
4731
|
-
}
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
5179
|
+
function placeTooltip(input) {
|
|
5180
|
+
const { anchor, viewportWidth, viewportHeight, side, align, gap } = input;
|
|
5181
|
+
let { width, height } = input;
|
|
5182
|
+
const margin = 12;
|
|
5183
|
+
const maxWidth = Math.max(0, viewportWidth - margin * 2);
|
|
5184
|
+
const maxHeight = Math.max(0, viewportHeight - margin * 2);
|
|
5185
|
+
width = Math.min(width, maxWidth);
|
|
5186
|
+
height = Math.min(height, maxHeight);
|
|
5187
|
+
const clampX = (x) => Math.max(margin, Math.min(x, viewportWidth - margin - width));
|
|
5188
|
+
const clampY = (y) => Math.max(margin, Math.min(y, viewportHeight - margin - height));
|
|
5189
|
+
const horizontal = side === "right" || side === "left";
|
|
5190
|
+
const candidates = [
|
|
5191
|
+
side,
|
|
5192
|
+
opposite[side],
|
|
5193
|
+
...horizontal ? ["bottom", "top"] : ["right", "left"]
|
|
5194
|
+
];
|
|
5195
|
+
const positions = {
|
|
5196
|
+
right: {
|
|
5197
|
+
left: anchor.right + 10,
|
|
5198
|
+
top: clampY((anchor.top + anchor.bottom - height) / 2)
|
|
5199
|
+
},
|
|
5200
|
+
left: {
|
|
5201
|
+
left: anchor.left - 10 - width,
|
|
5202
|
+
top: clampY((anchor.top + anchor.bottom - height) / 2)
|
|
5203
|
+
},
|
|
5204
|
+
bottom: {
|
|
5205
|
+
left: clampX(align === "end" ? anchor.right - width : (anchor.left + anchor.right - width) / 2),
|
|
5206
|
+
top: anchor.bottom + gap
|
|
5207
|
+
},
|
|
5208
|
+
top: {
|
|
5209
|
+
left: clampX(align === "end" ? anchor.right - width : (anchor.left + anchor.right - width) / 2),
|
|
5210
|
+
top: anchor.top - gap - height
|
|
5211
|
+
}
|
|
5212
|
+
};
|
|
5213
|
+
for (const candidate of candidates) {
|
|
5214
|
+
const position = positions[candidate];
|
|
5215
|
+
if (position.left >= margin && position.top >= margin && position.left + width <= viewportWidth - margin && position.top + height <= viewportHeight - margin) return {
|
|
5216
|
+
...position,
|
|
5217
|
+
side: candidate,
|
|
5218
|
+
width,
|
|
5219
|
+
height,
|
|
5220
|
+
overflow: false
|
|
5221
|
+
};
|
|
5222
|
+
}
|
|
5223
|
+
const position = positions[side];
|
|
5224
|
+
return {
|
|
5225
|
+
left: clampX(position.left),
|
|
5226
|
+
top: clampY(position.top),
|
|
5227
|
+
side,
|
|
5228
|
+
width,
|
|
5229
|
+
height,
|
|
5230
|
+
overflow: true
|
|
5231
|
+
};
|
|
4738
5232
|
}
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
visibility: "hidden",
|
|
4742
|
-
left: 0,
|
|
4743
|
-
top: 0
|
|
4744
|
-
};
|
|
4745
|
-
/**
|
|
4746
|
-
* Render an anchored dropdown menu. While the list is open its keys mirror the
|
|
4747
|
-
* composer's: Tab settles the focused row — from the trigger, Tab enters the
|
|
4748
|
-
* list instead — and Escape or Shift+Tab close it and return focus to the
|
|
4749
|
-
* anchor's first button, and selecting a row does the same — the rows unmount
|
|
4750
|
-
* with the list. Only a keyboard on the trigger or inside the list is
|
|
4751
|
-
* intercepted; Tab presses elsewhere on the page stay the browser's.
|
|
4752
|
-
* @param props.autoFocus - focus the first item on open; the arrow keys walk the list either way.
|
|
4753
|
-
* @param props.open - whether the list is showing (owner-controlled).
|
|
4754
|
-
* @param props.anchor - the trigger element (rendered in place).
|
|
4755
|
-
* @param props.items - selectable data rows and optional separators (default none; with no `children` either, the list is empty).
|
|
4756
|
-
* @param props.selectedId - row shown as selected.
|
|
4757
|
-
* @param props.selectedIds - rows shown as selected when a menu contains independent option groups.
|
|
4758
|
-
* @param props.onSelect - data-row activation callback (not called for disabled rows or submenu parents that only open children).
|
|
4759
|
-
* @param props.onClose - invoked on outside click, Escape, or a window blur
|
|
4760
|
-
* that moved focus into an iframe (the only signal a pointerdown inside a
|
|
4761
|
-
* cross-origin iframe leaves).
|
|
4762
|
-
* @param props.align - list alignment against the anchor (default 'start').
|
|
4763
|
-
* @param props.side - open below (`bottom`, default) or above (`top`) the anchor.
|
|
4764
|
-
* @param props.portal - render the list into document.body, fixed-positioned
|
|
4765
|
-
* from the anchor rect (follows movement and resizing while open). Use when an
|
|
4766
|
-
* ancestor's overflow clipping would crop the in-place list; default false
|
|
4767
|
-
* keeps the pure-CSS in-place behavior.
|
|
4768
|
-
* @param props.closeOnPointerLeave - close the list once the pointer has left
|
|
4769
|
-
* both trigger and list for the pointer grace (default false keeps it open
|
|
4770
|
-
* until outside click/Escape/selection). The grace makes the 4px trigger->list
|
|
4771
|
-
* gap and a brief overshoot survivable; coming back cancels the close.
|
|
4772
|
-
* @param props.dense - reduce vertical row spacing without changing the standard typography or card width.
|
|
4773
|
-
* @param props.compact - use reduced menu typography and spacing.
|
|
4774
|
-
* @param props.getAnchorRect - portal mode only: supply the anchor rect
|
|
4775
|
-
* directly (e.g. from a host-owned trigger button) instead of measuring the
|
|
4776
|
-
* Menu's own wrapper span. Required when the wrapper isn't itself laid out at
|
|
4777
|
-
* the trigger (render-prop anchors, effect-positioned proxies — measuring the
|
|
4778
|
-
* wrapper there races the host's layout effects). Called on open, each animation
|
|
4779
|
-
* frame, and scroll/resize; return null to skip placement for that frame.
|
|
4780
|
-
* @param props.minWidthFromAnchor - in portal mode, keep the menu at least as wide as its anchor, bounded by viewport margins.
|
|
4781
|
-
* @param props.footer - rows pinned below the scrolling items area, separated
|
|
4782
|
-
* by a hairline; they stay visible while the items above scroll.
|
|
4783
|
-
* @param props.children - component rows rendered after `items` in the same
|
|
4784
|
-
* list, each a `role="menuitem"` button such as {@link MenuItemButton}; they
|
|
4785
|
-
* share the keyboard walk, the submenu exclusivity, and the post-selection
|
|
4786
|
-
* focus return.
|
|
4787
|
-
* @param props.selection - how a selected row is marked: a trailing check
|
|
4788
|
-
* (`'check'`, default — figma .Menu_cell) or the hover fill held on the row
|
|
4789
|
-
* with no check (`'fill'`, for icon-labelled rows where a trailing glyph
|
|
4790
|
-
* crowds the cell).
|
|
4791
|
-
* @param props.className - extra class on the anchor wrapper span.
|
|
4792
|
-
* @param props.listClassName - extra class on the dropdown card itself; the
|
|
4793
|
-
* only style hook that reaches a portaled list, which renders under
|
|
4794
|
-
* document.body outside the owner's DOM subtree.
|
|
4795
|
-
* @returns anchor wrapper with the conditional list.
|
|
4796
|
-
*/
|
|
4797
|
-
function Menu({ open, anchor, items = [], children, selectedId, selectedIds, onSelect, onClose, align = "start", side = "bottom", portal = false, closeOnPointerLeave = false, dense = false, compact = false, autoFocus = false, selection = "check", getAnchorRect, minWidthFromAnchor = false, footer, className, listClassName }) {
|
|
4798
|
-
const rootRef = useRef(null);
|
|
4799
|
-
const listRef = useRef(null);
|
|
4800
|
-
/** Index the arrow walk last focused, the resume point when focus left the rows. */
|
|
4801
|
-
const walkIndex = useRef(null);
|
|
4802
|
-
/**
|
|
4803
|
-
* The control that had the keyboard when this menu opened — its own trigger,
|
|
4804
|
-
* which an anchor that wraps several controls (a split button) would not be
|
|
4805
|
-
* able to name by position.
|
|
4806
|
-
*/
|
|
4807
|
-
const triggerRef = useRef(null);
|
|
4808
|
-
const selectingWithTab = useRef(false);
|
|
4809
|
-
/**
|
|
4810
|
-
* Hand the keyboard back to the trigger that opened the menu — or, when the
|
|
4811
|
-
* anchor never held it, to the anchor's first button. Focus left on a removed
|
|
4812
|
-
* row otherwise falls to the page body, where the next Tab restarts from the
|
|
4813
|
-
* top of the page.
|
|
4814
|
-
* @param navigation - whether explicit keyboard traversal should retain its focus indicator.
|
|
4815
|
-
*/
|
|
4816
|
-
const refocusAnchor = (navigation = false) => {
|
|
4817
|
-
const trigger = triggerRef.current;
|
|
4818
|
-
const target = trigger !== null && document.contains(trigger) && !trigger.disabled ? trigger : rootRef.current?.querySelector("button:not(:disabled)");
|
|
4819
|
-
if (target == null) return;
|
|
4820
|
-
if (navigation) target.focus();
|
|
4821
|
-
else focusWithoutRing(target);
|
|
4822
|
-
};
|
|
4823
|
-
/**
|
|
4824
|
-
* Post-selection focus, for the paths where the rows unmount with the list.
|
|
4825
|
-
* A selection whose owner keeps the menu open is left alone, and so is an
|
|
4826
|
-
* owner that moved focus itself (a presented file card hands it to its
|
|
4827
|
-
* preview button): only a keyboard left on the closing list (or on the body
|
|
4828
|
-
* its removal produced) comes back to the trigger.
|
|
4829
|
-
*/
|
|
4830
|
-
const refocusAfterSelection = () => {
|
|
4831
|
-
const navigation = selectingWithTab.current;
|
|
4832
|
-
queueMicrotask(() => {
|
|
4833
|
-
if (openRef.current) return;
|
|
4834
|
-
const active = document.activeElement;
|
|
4835
|
-
if (active === null || active === document.body || listRef.current?.contains(active) === true) refocusAnchor(navigation);
|
|
4836
|
-
});
|
|
4837
|
-
};
|
|
4838
|
-
const openRef = useRef(open);
|
|
4839
|
-
openRef.current = open;
|
|
4840
|
-
const [openSubmenuId, setOpenSubmenuId] = useState(null);
|
|
4841
|
-
const [fixedPos, setFixedPos] = useState(null);
|
|
4842
|
-
const { arm: armClose, cancel: cancelClose } = usePointerGrace(onClose);
|
|
4843
|
-
useLayoutEffect(() => {
|
|
4844
|
-
if (!open || !portal) {
|
|
4845
|
-
setFixedPos(null);
|
|
4846
|
-
return;
|
|
4847
|
-
}
|
|
4848
|
-
const place = () => {
|
|
4849
|
-
let r;
|
|
4850
|
-
if (getAnchorRect !== void 0) r = getAnchorRect();
|
|
4851
|
-
else
|
|
4852
|
-
/* v8 ignore next 2 -- the ref is attached before the layout effect runs and the listeners die with it. */
|
|
4853
|
-
r = rootRef.current?.getBoundingClientRect() ?? null;
|
|
4854
|
-
if (r === null) return;
|
|
4855
|
-
const MARGIN = 12;
|
|
4856
|
-
const vw = window.innerWidth;
|
|
4857
|
-
const vh = window.innerHeight;
|
|
4858
|
-
const listEl = listRef.current;
|
|
4859
|
-
if (listEl !== null) {
|
|
4860
|
-
const minimum = minWidthFromAnchor ? `${Math.min(r.width, Math.max(0, vw - MARGIN * 2))}px` : "";
|
|
4861
|
-
if (listEl.style.minWidth !== minimum) listEl.style.minWidth = minimum;
|
|
4862
|
-
}
|
|
4863
|
-
const lw = listEl?.offsetWidth ?? 0;
|
|
4864
|
-
const lh = listEl?.offsetHeight ?? 0;
|
|
4865
|
-
let x;
|
|
4866
|
-
let y;
|
|
4867
|
-
if (side === "right") {
|
|
4868
|
-
x = r.right + 4;
|
|
4869
|
-
y = r.top;
|
|
4870
|
-
} else if (align === "start") {
|
|
4871
|
-
x = r.left;
|
|
4872
|
-
y = side === "bottom" ? r.bottom + 4 : r.top - lh - 4;
|
|
4873
|
-
} else {
|
|
4874
|
-
x = r.right - lw;
|
|
4875
|
-
y = side === "bottom" ? r.bottom + 4 : r.top - lh - 4;
|
|
4876
|
-
}
|
|
4877
|
-
if (lw > 0) x = Math.min(Math.max(x, MARGIN), vw - lw - MARGIN);
|
|
4878
|
-
if (lh > 0) y = Math.min(Math.max(y, overlayTopMargin(MARGIN)), vh - lh - MARGIN);
|
|
4879
|
-
setFixedPos((current) => current?.left === x && current.top === y ? current : {
|
|
4880
|
-
left: x,
|
|
4881
|
-
top: y
|
|
4882
|
-
});
|
|
4883
|
-
};
|
|
4884
|
-
place();
|
|
4885
|
-
const track = () => {
|
|
4886
|
-
place();
|
|
4887
|
-
frame = requestAnimationFrame(track);
|
|
4888
|
-
};
|
|
4889
|
-
let frame = requestAnimationFrame(track);
|
|
4890
|
-
window.addEventListener("scroll", place, true);
|
|
4891
|
-
window.addEventListener("resize", place);
|
|
4892
|
-
return () => {
|
|
4893
|
-
cancelAnimationFrame(frame);
|
|
4894
|
-
window.removeEventListener("scroll", place, true);
|
|
4895
|
-
window.removeEventListener("resize", place);
|
|
4896
|
-
};
|
|
4897
|
-
}, [
|
|
4898
|
-
open,
|
|
4899
|
-
portal,
|
|
4900
|
-
align,
|
|
4901
|
-
side,
|
|
4902
|
-
getAnchorRect,
|
|
4903
|
-
minWidthFromAnchor
|
|
4904
|
-
]);
|
|
4905
|
-
useEffect(() => {
|
|
4906
|
-
if (!open) {
|
|
4907
|
-
triggerRef.current = null;
|
|
4908
|
-
return;
|
|
4909
|
-
}
|
|
4910
|
-
const active = document.activeElement;
|
|
4911
|
-
triggerRef.current = active instanceof HTMLElement && rootRef.current?.contains(active) === true ? active : null;
|
|
4912
|
-
}, [open]);
|
|
4913
|
-
useEffect(() => {
|
|
4914
|
-
if (!open || !autoFocus) return;
|
|
4915
|
-
const first = listRef.current?.querySelector("button:not(:disabled)");
|
|
4916
|
-
walkIndex.current = first === void 0 || first === null ? null : 0;
|
|
4917
|
-
if (first != null) focusWithoutRing(first);
|
|
4918
|
-
}, [open, autoFocus]);
|
|
4919
|
-
useEffect(() => {
|
|
4920
|
-
if (!open) {
|
|
4921
|
-
setOpenSubmenuId(null);
|
|
4922
|
-
walkIndex.current = null;
|
|
4923
|
-
return;
|
|
4924
|
-
}
|
|
4925
|
-
const composition = observeComposition(document);
|
|
4926
|
-
const onPointerDown = (e) => {
|
|
4927
|
-
if (!(e.target instanceof Node)) return;
|
|
4928
|
-
if (rootRef.current?.contains(e.target) === true) return;
|
|
4929
|
-
if (listRef.current?.contains(e.target) === true) return;
|
|
4930
|
-
onClose();
|
|
4931
|
-
};
|
|
4932
|
-
const onKeyDown = (e) => {
|
|
4933
|
-
if (composition.guards(e) || isBehindModal(rootRef.current) || e.defaultPrevented || e.ctrlKey || e.altKey || e.metaKey) return;
|
|
4934
|
-
const focused = document.activeElement;
|
|
4935
|
-
const insideList = listRef.current?.contains(focused) === true;
|
|
4936
|
-
const anchored = rootRef.current?.contains(focused) === true || insideList;
|
|
4937
|
-
if (e.key === "Escape" && !e.shiftKey) {
|
|
4938
|
-
e.preventDefault();
|
|
4939
|
-
if (e.repeat) return;
|
|
4940
|
-
onClose();
|
|
4941
|
-
if (anchored || autoFocus) refocusAnchor();
|
|
4942
|
-
}
|
|
4943
|
-
if (e.key === "Tab") {
|
|
4944
|
-
const list = listRef.current;
|
|
4945
|
-
if (list === null || !anchored) return;
|
|
4946
|
-
if (e.shiftKey) {
|
|
4947
|
-
e.preventDefault();
|
|
4948
|
-
onClose();
|
|
4949
|
-
refocusAnchor(true);
|
|
4950
|
-
return;
|
|
4951
|
-
}
|
|
4952
|
-
if (insideList) {
|
|
4953
|
-
if (focused instanceof Element && focused.getAttribute("role") === "menuitem") {
|
|
4954
|
-
e.preventDefault();
|
|
4955
|
-
selectingWithTab.current = true;
|
|
4956
|
-
try {
|
|
4957
|
-
focused.click();
|
|
4958
|
-
} finally {
|
|
4959
|
-
selectingWithTab.current = false;
|
|
4960
|
-
}
|
|
4961
|
-
}
|
|
4962
|
-
return;
|
|
4963
|
-
}
|
|
4964
|
-
const row = list.querySelector("button:not(:disabled)");
|
|
4965
|
-
if (row === null) return;
|
|
4966
|
-
e.preventDefault();
|
|
4967
|
-
row.focus();
|
|
4968
|
-
walkIndex.current = 0;
|
|
4969
|
-
return;
|
|
4970
|
-
}
|
|
4971
|
-
if (![
|
|
4972
|
-
"ArrowDown",
|
|
4973
|
-
"ArrowUp",
|
|
4974
|
-
"Home",
|
|
4975
|
-
"End"
|
|
4976
|
-
].includes(e.key)) return;
|
|
4977
|
-
const list = listRef.current;
|
|
4978
|
-
if (list === null || !anchored) return;
|
|
4979
|
-
const buttons = Array.from(list.querySelectorAll("button:not(:disabled)"));
|
|
4980
|
-
if (buttons.length === 0) return;
|
|
4981
|
-
const index = buttons.indexOf(focused);
|
|
4982
|
-
const from = index >= 0 ? index : walkIndex.current;
|
|
4983
|
-
const next = e.key === "Home" ? 0 : e.key === "End" ? buttons.length - 1 : from === null ? e.key === "ArrowDown" ? 0 : buttons.length - 1 : (from + (e.key === "ArrowDown" ? 1 : -1) + buttons.length) % buttons.length;
|
|
4984
|
-
e.preventDefault();
|
|
4985
|
-
walkIndex.current = next;
|
|
4986
|
-
buttons[next]?.focus();
|
|
4987
|
-
};
|
|
4988
|
-
const onWindowBlur = () => {
|
|
4989
|
-
if (document.activeElement instanceof HTMLIFrameElement) onClose();
|
|
4990
|
-
};
|
|
4991
|
-
document.addEventListener("pointerdown", onPointerDown);
|
|
4992
|
-
const onEscape = (event) => {
|
|
4993
|
-
if (event.key === "Escape") onKeyDown(event);
|
|
4994
|
-
};
|
|
4995
|
-
const onOtherKey = (event) => {
|
|
4996
|
-
if (event.key !== "Escape") onKeyDown(event);
|
|
4997
|
-
};
|
|
4998
|
-
document.addEventListener("keydown", onOtherKey);
|
|
4999
|
-
document.addEventListener("keydown", onEscape, true);
|
|
5000
|
-
window.addEventListener("blur", onWindowBlur);
|
|
5001
|
-
return () => {
|
|
5002
|
-
composition.dispose();
|
|
5003
|
-
document.removeEventListener("pointerdown", onPointerDown);
|
|
5004
|
-
document.removeEventListener("keydown", onOtherKey);
|
|
5005
|
-
document.removeEventListener("keydown", onEscape, true);
|
|
5006
|
-
window.removeEventListener("blur", onWindowBlur);
|
|
5007
|
-
};
|
|
5008
|
-
}, [
|
|
5009
|
-
open,
|
|
5010
|
-
onClose,
|
|
5011
|
-
autoFocus
|
|
5012
|
-
]);
|
|
5013
|
-
useEffect(() => {
|
|
5014
|
-
if (!open) cancelClose();
|
|
5015
|
-
}, [open, cancelClose]);
|
|
5016
|
-
const scrollable = !items.some((entry) => !isSeparator(entry) && !isLabel(entry) && entry.submenu !== void 0 && entry.submenu.length > 0);
|
|
5017
|
-
const renderEntry = (entry) => {
|
|
5018
|
-
if (isSeparator(entry)) return jsx("div", {
|
|
5019
|
-
className: css$17.separator,
|
|
5020
|
-
role: "separator"
|
|
5021
|
-
}, entry.id);
|
|
5022
|
-
if (isLabel(entry)) return jsx("div", {
|
|
5023
|
-
className: css$17.label,
|
|
5024
|
-
role: "presentation",
|
|
5025
|
-
children: entry.text
|
|
5026
|
-
}, entry.id);
|
|
5027
|
-
const hasSub = entry.submenu !== void 0 && entry.submenu.length > 0;
|
|
5028
|
-
const subOpen = hasSub && openSubmenuId === entry.id;
|
|
5029
|
-
const selected = entry.id === selectedId || selectedIds?.includes(entry.id) === true;
|
|
5030
|
-
return jsxs("div", {
|
|
5031
|
-
className: css$17.itemWrap,
|
|
5032
|
-
onMouseEnter: hasSub ? () => {
|
|
5033
|
-
setOpenSubmenuId(entry.id);
|
|
5034
|
-
} : void 0,
|
|
5035
|
-
onMouseLeave: () => {
|
|
5036
|
-
setOpenSubmenuId(null);
|
|
5037
|
-
},
|
|
5038
|
-
children: [jsxs("button", {
|
|
5039
|
-
type: "button",
|
|
5040
|
-
role: "menuitem",
|
|
5041
|
-
className: clsx(css$17.item, selected && (selection === "fill" ? css$17.selectedFill : css$17.selected), entry.danger === true && css$17.danger),
|
|
5042
|
-
disabled: entry.disabled,
|
|
5043
|
-
"aria-keyshortcuts": entry.shortcut?.aria,
|
|
5044
|
-
"aria-haspopup": hasSub ? "menu" : void 0,
|
|
5045
|
-
"aria-expanded": hasSub ? subOpen : void 0,
|
|
5046
|
-
onFocus: hasSub ? () => {
|
|
5047
|
-
setOpenSubmenuId(entry.id);
|
|
5048
|
-
} : void 0,
|
|
5049
|
-
onClick: () => {
|
|
5050
|
-
if (hasSub) {
|
|
5051
|
-
setOpenSubmenuId(entry.id);
|
|
5052
|
-
return;
|
|
5053
|
-
}
|
|
5054
|
-
onSelect?.(entry.id);
|
|
5055
|
-
},
|
|
5056
|
-
children: [
|
|
5057
|
-
entry.icon !== void 0 && jsx("span", {
|
|
5058
|
-
className: css$17.itemIcon,
|
|
5059
|
-
children: entry.icon
|
|
5060
|
-
}),
|
|
5061
|
-
jsx("span", {
|
|
5062
|
-
className: css$17.itemLabel,
|
|
5063
|
-
children: entry.label
|
|
5064
|
-
}),
|
|
5065
|
-
entry.shortcut !== void 0 && jsx("span", {
|
|
5066
|
-
"aria-hidden": "true",
|
|
5067
|
-
className: css$17.shortcut,
|
|
5068
|
-
children: jsx(ShortcutKeys, {
|
|
5069
|
-
keys: entry.shortcut.keys,
|
|
5070
|
-
className: css$17.shortcutKeys
|
|
5071
|
-
})
|
|
5072
|
-
}),
|
|
5073
|
-
selected && selection === "check" && jsx(IconCheckOutlineRegular, { className: css$17.check })
|
|
5074
|
-
]
|
|
5075
|
-
}), subOpen && entry.submenu !== void 0 && jsx(MenuSurface, {
|
|
5076
|
-
compact,
|
|
5077
|
-
className: clsx(css$17.submenu, compact && css$17.compactList),
|
|
5078
|
-
role: "menu",
|
|
5079
|
-
children: entry.submenu.map((sub) => jsxs("button", {
|
|
5080
|
-
type: "button",
|
|
5081
|
-
role: "menuitem",
|
|
5082
|
-
className: css$17.item,
|
|
5083
|
-
disabled: sub.disabled,
|
|
5084
|
-
"aria-keyshortcuts": sub.shortcut?.aria,
|
|
5085
|
-
onClick: () => {
|
|
5086
|
-
onSelect?.(sub.id);
|
|
5087
|
-
refocusAfterSelection();
|
|
5088
|
-
},
|
|
5089
|
-
children: [
|
|
5090
|
-
sub.icon !== void 0 && jsx("span", {
|
|
5091
|
-
className: css$17.itemIcon,
|
|
5092
|
-
children: sub.icon
|
|
5093
|
-
}),
|
|
5094
|
-
jsx("span", {
|
|
5095
|
-
className: css$17.itemLabel,
|
|
5096
|
-
children: sub.label
|
|
5097
|
-
}),
|
|
5098
|
-
sub.shortcut !== void 0 && jsx("span", {
|
|
5099
|
-
"aria-hidden": "true",
|
|
5100
|
-
className: css$17.shortcut,
|
|
5101
|
-
children: jsx(ShortcutKeys, {
|
|
5102
|
-
keys: sub.shortcut.keys,
|
|
5103
|
-
className: css$17.shortcutKeys
|
|
5104
|
-
})
|
|
5105
|
-
})
|
|
5106
|
-
]
|
|
5107
|
-
}, sub.id))
|
|
5108
|
-
})]
|
|
5109
|
-
}, entry.id);
|
|
5110
|
-
};
|
|
5111
|
-
const collapseSubmenuFrom = (e) => {
|
|
5112
|
-
const row = e.target instanceof Element ? e.target.closest("button[role=\"menuitem\"]") : null;
|
|
5113
|
-
if (row === null || row.getAttribute("aria-haspopup") === "menu") return;
|
|
5114
|
-
if (row.closest("[role=\"menu\"]") !== e.currentTarget) return;
|
|
5115
|
-
setOpenSubmenuId(null);
|
|
5116
|
-
};
|
|
5117
|
-
const list = open && jsxs(MenuSurface, {
|
|
5118
|
-
compact,
|
|
5119
|
-
ref: listRef,
|
|
5120
|
-
className: clsx(css$17.list, listClassName, dense && css$17.denseList, compact && css$17.compactList, scrollable && css$17.scrollable, portal && css$17.portal, side === "top" && !portal && css$17.sideTop, align === "end" && !portal && css$17.alignEnd),
|
|
5121
|
-
style: portal ? fixedPos ?? MEASURE_STYLE : void 0,
|
|
5122
|
-
role: "menu",
|
|
5123
|
-
onClick: (e) => {
|
|
5124
|
-
e.stopPropagation();
|
|
5125
|
-
const row = e.target instanceof Element ? e.target.closest("button[role=\"menuitem\"]") : null;
|
|
5126
|
-
if (row !== null && row.getAttribute("aria-haspopup") !== "menu") refocusAfterSelection();
|
|
5127
|
-
},
|
|
5128
|
-
onMouseOver: collapseSubmenuFrom,
|
|
5129
|
-
onFocus: collapseSubmenuFrom,
|
|
5130
|
-
children: [jsxs("div", {
|
|
5131
|
-
className: css$17.viewport,
|
|
5132
|
-
role: "presentation",
|
|
5133
|
-
children: [items.map(renderEntry), children]
|
|
5134
|
-
}), footer !== void 0 && footer.length > 0 && jsx("div", {
|
|
5135
|
-
className: css$17.footer,
|
|
5136
|
-
role: "presentation",
|
|
5137
|
-
children: footer.map(renderEntry)
|
|
5138
|
-
})]
|
|
5139
|
-
});
|
|
5140
|
-
return jsxs("span", {
|
|
5141
|
-
ref: rootRef,
|
|
5142
|
-
className: clsx(css$17.root, className),
|
|
5143
|
-
onPointerEnter: closeOnPointerLeave ? cancelClose : void 0,
|
|
5144
|
-
onPointerLeave: closeOnPointerLeave ? () => {
|
|
5145
|
-
if (open) armClose();
|
|
5146
|
-
} : void 0,
|
|
5147
|
-
children: [anchor, portal ? list !== false && createPortal(list, document.body) : list]
|
|
5148
|
-
});
|
|
5149
|
-
}
|
|
5150
|
-
//#endregion
|
|
5151
|
-
//#region lib/types/useAnchoredMaxHeight.js
|
|
5152
|
-
/**
|
|
5153
|
-
* Viewport-fit hook for bottom-anchored overlays (slash menu, popupSelect):
|
|
5154
|
-
* the element's bottom edge is laid out independent of its height, so it
|
|
5155
|
-
* grows upward and only the top edge can collide with the viewport — clamp
|
|
5156
|
-
* the design cap to the space between that edge and the viewport top.
|
|
5157
|
-
*/
|
|
5158
|
-
/** Safe distance kept between the overlay and the viewport top edge (mirrors the Menu portal margin). */
|
|
5159
|
-
const MARGIN = 12;
|
|
5160
|
-
/**
|
|
5161
|
-
* Clamp a bottom-anchored overlay's max-height to the viewport.
|
|
5162
|
-
* @param ref - the overlay element; a null current (overlay closed) skips measuring.
|
|
5163
|
-
* @param cap - design max-height in px (the clamp never exceeds it).
|
|
5164
|
-
* @param signal - re-measure trigger: pass the overlay's render state so anchor
|
|
5165
|
-
* moves (composer growth) re-fit; resize/scroll re-fit while mounted.
|
|
5166
|
-
* @returns the max-height to apply inline, in px.
|
|
5167
|
-
*/
|
|
5168
|
-
function useAnchoredMaxHeight(ref, cap, signal) {
|
|
5169
|
-
const [maxHeight, setMaxHeight] = useState(cap);
|
|
5170
|
-
useLayoutEffect(() => {
|
|
5171
|
-
const el = ref.current;
|
|
5172
|
-
if (el === null) return;
|
|
5173
|
-
const fit = () => {
|
|
5174
|
-
setMaxHeight(Math.min(cap, Math.max(0, el.getBoundingClientRect().bottom - MARGIN)));
|
|
5175
|
-
};
|
|
5176
|
-
fit();
|
|
5177
|
-
window.addEventListener("resize", fit);
|
|
5178
|
-
window.addEventListener("scroll", fit, true);
|
|
5179
|
-
return () => {
|
|
5180
|
-
window.removeEventListener("resize", fit);
|
|
5181
|
-
window.removeEventListener("scroll", fit, true);
|
|
5182
|
-
};
|
|
5183
|
-
}, [
|
|
5184
|
-
ref,
|
|
5185
|
-
cap,
|
|
5186
|
-
signal
|
|
5187
|
-
]);
|
|
5188
|
-
return maxHeight;
|
|
5189
|
-
}
|
|
5190
|
-
//#endregion
|
|
5191
|
-
//#region lib/types/useAnchoredPosition.js
|
|
5192
|
-
/**
|
|
5193
|
-
* Keep a fixed-position floating element anchored to a trigger.
|
|
5194
|
-
*
|
|
5195
|
-
* A portaled panel is positioned from its anchor's viewport rect, which stops
|
|
5196
|
-
* being true the moment anything scrolls or the window resizes. This owns that
|
|
5197
|
-
* one concern: measure the anchor, offset the panel below or above it, clamp
|
|
5198
|
-
* the result inside the viewport, and re-run on scroll (capture phase, so
|
|
5199
|
-
* scrollers nested inside the page are caught too), on resize, and on the
|
|
5200
|
-
* panel's own size changes while the element is open.
|
|
5201
|
-
* @module @qilin-agent/client-ui-primitives/useAnchoredPosition
|
|
5202
|
-
*/
|
|
5203
|
-
/**
|
|
5204
|
-
* Track an anchor and return the panel's fixed coordinates.
|
|
5205
|
-
* @param options - the open state, the two refs, the placement side and alignment, and the gap/margin distances.
|
|
5206
|
-
* @returns `left`/`top` for the panel, or `null` before the first measurement.
|
|
5207
|
-
*/
|
|
5208
|
-
function useAnchoredPosition(options) {
|
|
5209
|
-
const { open, anchorRef, panelRef, side = "bottom", align = "start", gap, margin } = options;
|
|
5210
|
-
const [position, setPosition] = useState(null);
|
|
5211
|
-
useLayoutEffect(() => {
|
|
5212
|
-
if (!open) {
|
|
5213
|
-
setPosition(null);
|
|
5214
|
-
return;
|
|
5215
|
-
}
|
|
5216
|
-
const place = () => {
|
|
5217
|
-
/* v8 ignore start -- geometry read from real layout: jsdom reports zero
|
|
5218
|
-
offset sizes, so the positive-size clamp arms are exercised by browser
|
|
5219
|
-
scenarios rather than unit tests. */
|
|
5220
|
-
const rect = anchorRef.current?.getBoundingClientRect();
|
|
5221
|
-
if (rect === void 0) return;
|
|
5222
|
-
const panel = panelRef.current;
|
|
5223
|
-
const width = panel?.offsetWidth ?? 0;
|
|
5224
|
-
const height = panel?.offsetHeight ?? 0;
|
|
5225
|
-
let left = align === "end" ? rect.right - width : rect.left;
|
|
5226
|
-
let top = side === "top" ? rect.top - gap - height : rect.bottom + gap;
|
|
5227
|
-
if (width > 0) left = Math.min(Math.max(left, margin), window.innerWidth - width - margin);
|
|
5228
|
-
if (height > 0) top = Math.min(Math.max(top, margin), window.innerHeight - height - margin);
|
|
5229
|
-
/* v8 ignore stop */
|
|
5230
|
-
setPosition({
|
|
5231
|
-
left,
|
|
5232
|
-
top
|
|
5233
|
-
});
|
|
5234
|
-
};
|
|
5235
|
-
place();
|
|
5236
|
-
window.addEventListener("scroll", place, true);
|
|
5237
|
-
window.addEventListener("resize", place);
|
|
5238
|
-
const panel = panelRef.current;
|
|
5239
|
-
let observer = null;
|
|
5240
|
-
if (typeof ResizeObserver !== "undefined" && panel !== null) {
|
|
5241
|
-
observer = new ResizeObserver(place);
|
|
5242
|
-
observer.observe(panel);
|
|
5243
|
-
}
|
|
5244
|
-
return () => {
|
|
5245
|
-
observer?.disconnect();
|
|
5246
|
-
window.removeEventListener("scroll", place, true);
|
|
5247
|
-
window.removeEventListener("resize", place);
|
|
5248
|
-
};
|
|
5249
|
-
}, [
|
|
5250
|
-
open,
|
|
5251
|
-
anchorRef,
|
|
5252
|
-
panelRef,
|
|
5253
|
-
side,
|
|
5254
|
-
align,
|
|
5255
|
-
gap,
|
|
5256
|
-
margin
|
|
5257
|
-
]);
|
|
5258
|
-
return position;
|
|
5259
|
-
}
|
|
5260
|
-
//#endregion
|
|
5261
|
-
//#region lib/types/useDismissOnOutsidePointer.js
|
|
5233
|
+
//#endregion
|
|
5234
|
+
//#region lib/types/useDismissOnOutsidePointer.js
|
|
5262
5235
|
/**
|
|
5263
5236
|
* Outside-pointer dismissal for trigger-owned popovers (jobs list, Cordis
|
|
5264
5237
|
* panel): while the surface is open, a pointerdown outside the root closes it.
|
|
@@ -5289,106 +5262,6 @@ function useDismissOnOutsidePointer(root, open, setOpen, portal) {
|
|
|
5289
5262
|
]);
|
|
5290
5263
|
}
|
|
5291
5264
|
//#endregion
|
|
5292
|
-
//#region lib/types/clipboard.js
|
|
5293
|
-
/**
|
|
5294
|
-
* Write text to the host clipboard, preferring the async Clipboard API and
|
|
5295
|
-
* falling back to `execCommand('copy')` on hosts (jsdom, insecure contexts)
|
|
5296
|
-
* that omit it.
|
|
5297
|
-
* @param text - the exact text to place on the clipboard.
|
|
5298
|
-
* @returns true only when the host accepted the write.
|
|
5299
|
-
*/
|
|
5300
|
-
async function writeClipboard(text) {
|
|
5301
|
-
if (navigator.clipboard?.writeText) try {
|
|
5302
|
-
await navigator.clipboard.writeText(text);
|
|
5303
|
-
return true;
|
|
5304
|
-
} catch {
|
|
5305
|
-
return false;
|
|
5306
|
-
}
|
|
5307
|
-
const exec = typeof document.execCommand === "function" ? document.execCommand.bind(document) : void 0;
|
|
5308
|
-
if (exec === void 0) return false;
|
|
5309
|
-
const el = document.createElement("textarea");
|
|
5310
|
-
el.value = text;
|
|
5311
|
-
el.setAttribute("readonly", "");
|
|
5312
|
-
el.style.position = "fixed";
|
|
5313
|
-
el.style.left = "-9999px";
|
|
5314
|
-
document.body.appendChild(el);
|
|
5315
|
-
el.select();
|
|
5316
|
-
try {
|
|
5317
|
-
return exec("copy");
|
|
5318
|
-
} catch {
|
|
5319
|
-
return false;
|
|
5320
|
-
} finally {
|
|
5321
|
-
el.remove();
|
|
5322
|
-
}
|
|
5323
|
-
}
|
|
5324
|
-
//#endregion
|
|
5325
|
-
//#region lib/types/tooltip-placement.js
|
|
5326
|
-
const opposite = {
|
|
5327
|
-
right: "left",
|
|
5328
|
-
left: "right",
|
|
5329
|
-
top: "bottom",
|
|
5330
|
-
bottom: "top"
|
|
5331
|
-
};
|
|
5332
|
-
/**
|
|
5333
|
-
* Prefer the requested side, its opposite, then perpendicular sides without covering the anchor.
|
|
5334
|
-
* @param input - Observed dimensions, anchor edges, viewport and requested placement; horizontal gaps remain 10px.
|
|
5335
|
-
* @returns Border-box coordinates and the selected side; if no side fits, a viewport-bounded fallback.
|
|
5336
|
-
*/
|
|
5337
|
-
function placeTooltip(input) {
|
|
5338
|
-
const { anchor, viewportWidth, viewportHeight, side, align, gap } = input;
|
|
5339
|
-
let { width, height } = input;
|
|
5340
|
-
const margin = 12;
|
|
5341
|
-
const maxWidth = Math.max(0, viewportWidth - margin * 2);
|
|
5342
|
-
const maxHeight = Math.max(0, viewportHeight - margin * 2);
|
|
5343
|
-
width = Math.min(width, maxWidth);
|
|
5344
|
-
height = Math.min(height, maxHeight);
|
|
5345
|
-
const clampX = (x) => Math.max(margin, Math.min(x, viewportWidth - margin - width));
|
|
5346
|
-
const clampY = (y) => Math.max(margin, Math.min(y, viewportHeight - margin - height));
|
|
5347
|
-
const horizontal = side === "right" || side === "left";
|
|
5348
|
-
const candidates = [
|
|
5349
|
-
side,
|
|
5350
|
-
opposite[side],
|
|
5351
|
-
...horizontal ? ["bottom", "top"] : ["right", "left"]
|
|
5352
|
-
];
|
|
5353
|
-
const positions = {
|
|
5354
|
-
right: {
|
|
5355
|
-
left: anchor.right + 10,
|
|
5356
|
-
top: clampY((anchor.top + anchor.bottom - height) / 2)
|
|
5357
|
-
},
|
|
5358
|
-
left: {
|
|
5359
|
-
left: anchor.left - 10 - width,
|
|
5360
|
-
top: clampY((anchor.top + anchor.bottom - height) / 2)
|
|
5361
|
-
},
|
|
5362
|
-
bottom: {
|
|
5363
|
-
left: clampX(align === "end" ? anchor.right - width : (anchor.left + anchor.right - width) / 2),
|
|
5364
|
-
top: anchor.bottom + gap
|
|
5365
|
-
},
|
|
5366
|
-
top: {
|
|
5367
|
-
left: clampX(align === "end" ? anchor.right - width : (anchor.left + anchor.right - width) / 2),
|
|
5368
|
-
top: anchor.top - gap - height
|
|
5369
|
-
}
|
|
5370
|
-
};
|
|
5371
|
-
for (const candidate of candidates) {
|
|
5372
|
-
const position = positions[candidate];
|
|
5373
|
-
if (position.left >= margin && position.top >= margin && position.left + width <= viewportWidth - margin && position.top + height <= viewportHeight - margin) return {
|
|
5374
|
-
...position,
|
|
5375
|
-
side: candidate,
|
|
5376
|
-
width,
|
|
5377
|
-
height,
|
|
5378
|
-
overflow: false
|
|
5379
|
-
};
|
|
5380
|
-
}
|
|
5381
|
-
const position = positions[side];
|
|
5382
|
-
return {
|
|
5383
|
-
left: clampX(position.left),
|
|
5384
|
-
top: clampY(position.top),
|
|
5385
|
-
side,
|
|
5386
|
-
width,
|
|
5387
|
-
height,
|
|
5388
|
-
overflow: true
|
|
5389
|
-
};
|
|
5390
|
-
}
|
|
5391
|
-
//#endregion
|
|
5392
5265
|
//#region lib/types/input-modality.js
|
|
5393
5266
|
/**
|
|
5394
5267
|
* Document-wide input tracking shared by tooltips and focus-ring styles.
|
|
@@ -5460,6 +5333,9 @@ if (typeof window !== "undefined") {
|
|
|
5460
5333
|
* withdraws the enclosing tooltip while its bubble is shown.
|
|
5461
5334
|
*/
|
|
5462
5335
|
const TooltipSuppression = createContext(null);
|
|
5336
|
+
function clipped(anchor) {
|
|
5337
|
+
return [anchor, ...anchor.querySelectorAll("*")].some((el) => el instanceof HTMLElement && el.scrollWidth > el.clientWidth);
|
|
5338
|
+
}
|
|
5463
5339
|
/**
|
|
5464
5340
|
* Attach a hover/focus tooltip to an anchor element.
|
|
5465
5341
|
* @param props.label - bubble text, or a resolver evaluated only while visible; an empty string shows only shortcut keys.
|
|
@@ -5479,17 +5355,20 @@ const TooltipSuppression = createContext(null);
|
|
|
5479
5355
|
* toggling never remounts it (which would cut its CSS transitions).
|
|
5480
5356
|
* @param props.maxWidth - bubble width cap in pixels, for labels long enough that the default
|
|
5481
5357
|
* half-viewport cap would render a slab wider than the surface the anchor sits on; always bounded by viewport margins.
|
|
5482
|
-
* @param props.openOnClick - clicking also pins the bubble for reading; another click, Escape,
|
|
5358
|
+
* @param props.openOnClick - clicking or tapping also pins the bubble for reading; another click, Escape,
|
|
5483
5359
|
* Tab, or an outside pointerdown dismisses it. Defaults to false for ordinary action tooltips.
|
|
5360
|
+
* @param props.onlyWhenClipped - show the bubble only when the anchor or an HTML element inside it
|
|
5361
|
+
* overflows horizontally, measured when the bubble is about to show; for labels that repeat
|
|
5362
|
+
* text an ellipsis may cut off. Defaults to false.
|
|
5484
5363
|
* @param props.children - a single anchor element; its own ref (callback or object) is forwarded alongside the tooltip's.
|
|
5485
|
-
* An anchor that accepts no ref still positions the bubble: a raise records the element the event ran on.
|
|
5486
5364
|
* @returns the cloned anchor plus a fixed-position bubble, optionally portaled to the body.
|
|
5487
5365
|
* The bubble stays hidden until ResizeObserver supplies its size for viewport fitting; clicking the
|
|
5488
5366
|
* anchor dismisses the bubble unless openOnClick is enabled, and focus arriving after a pointer
|
|
5489
|
-
* interaction (a closing menu refocusing its trigger) never raises it.
|
|
5490
|
-
*
|
|
5367
|
+
* interaction (a closing menu refocusing its trigger) never raises it. Touch cannot trigger hover
|
|
5368
|
+
* tooltips; tapping still invokes the child's action and can open an openOnClick explanation.
|
|
5369
|
+
* When no side fits, the viewport-bounded fallback may overlap the anchor; oversized content is scrollable when pinned.
|
|
5491
5370
|
*/
|
|
5492
|
-
function Tooltip({ label, shortcutKeys, side = "right", align = "center", delayMs = 0, focusDelayMs = 0, gap = 8, disabled = false, portal = false, maxWidth, openOnClick = false, children }) {
|
|
5371
|
+
function Tooltip({ label, shortcutKeys, side = "right", align = "center", delayMs = 0, focusDelayMs = 0, gap = 8, disabled = false, portal = false, maxWidth, openOnClick = false, onlyWhenClipped = false, children }) {
|
|
5493
5372
|
const id = useId();
|
|
5494
5373
|
const [pinned, setPinned] = useState(false);
|
|
5495
5374
|
const anchor = useRef(null);
|
|
@@ -5499,10 +5378,6 @@ function Tooltip({ label, shortcutKeys, side = "right", align = "center", delayM
|
|
|
5499
5378
|
if (typeof childRef === "function") childRef(el);
|
|
5500
5379
|
else if (childRef != null) childRef.current = el;
|
|
5501
5380
|
}, [childRef]);
|
|
5502
|
-
const eventAnchor = useRef(null);
|
|
5503
|
-
const trackAnchor = (event) => {
|
|
5504
|
-
eventAnchor.current = event.currentTarget;
|
|
5505
|
-
};
|
|
5506
5381
|
const [pos, setPos] = useState(null);
|
|
5507
5382
|
const bubble = useRef(null);
|
|
5508
5383
|
const resolvedLabel = pos === null ? null : typeof label === "function" ? label() : label;
|
|
@@ -5543,8 +5418,7 @@ function Tooltip({ label, shortcutKeys, side = "right", align = "center", delayM
|
|
|
5543
5418
|
el.style.visibility = "visible";
|
|
5544
5419
|
};
|
|
5545
5420
|
const onResize = () => {
|
|
5546
|
-
|
|
5547
|
-
if (current !== null) anchorRect = current.getBoundingClientRect();
|
|
5421
|
+
if (anchor.current !== null) anchorRect = anchor.current.getBoundingClientRect();
|
|
5548
5422
|
fit();
|
|
5549
5423
|
};
|
|
5550
5424
|
const observer = new ResizeObserver((entries) => {
|
|
@@ -5595,9 +5469,10 @@ function Tooltip({ label, shortcutKeys, side = "right", align = "center", delayM
|
|
|
5595
5469
|
]);
|
|
5596
5470
|
const show = () => {
|
|
5597
5471
|
if (disabled) return;
|
|
5598
|
-
const el = anchor.current
|
|
5599
|
-
/* v8 ignore next --
|
|
5472
|
+
const el = anchor.current;
|
|
5473
|
+
/* v8 ignore next -- the ref is attached by event time: events fire on the cloned anchor. */
|
|
5600
5474
|
if (el === null) return;
|
|
5475
|
+
if (onlyWhenClipped && !clipped(el)) return;
|
|
5601
5476
|
setPos(el.getBoundingClientRect());
|
|
5602
5477
|
announce(true);
|
|
5603
5478
|
};
|
|
@@ -5627,96 +5502,693 @@ function Tooltip({ label, shortcutKeys, side = "right", align = "center", delayM
|
|
|
5627
5502
|
hover: false,
|
|
5628
5503
|
focus: false
|
|
5629
5504
|
};
|
|
5630
|
-
withdraw();
|
|
5631
|
-
}, [cancelShow, withdraw]);
|
|
5632
|
-
useDismissOnOutsidePointer(anchor, openOnClick && visible, dismiss, bubble);
|
|
5633
|
-
useEffect(() => {
|
|
5634
|
-
if (!openOnClick || !visible) return;
|
|
5635
|
-
const onKeyDown = (event) => {
|
|
5636
|
-
if (event.key !== "Escape" && event.key !== "Tab") return;
|
|
5637
|
-
if (event.key === "Escape") {
|
|
5638
|
-
event.preventDefault();
|
|
5639
|
-
event.stopPropagation();
|
|
5505
|
+
withdraw();
|
|
5506
|
+
}, [cancelShow, withdraw]);
|
|
5507
|
+
useDismissOnOutsidePointer(anchor, openOnClick && visible, dismiss, bubble);
|
|
5508
|
+
useEffect(() => {
|
|
5509
|
+
if (!openOnClick || !visible) return;
|
|
5510
|
+
const onKeyDown = (event) => {
|
|
5511
|
+
if (event.key !== "Escape" && event.key !== "Tab") return;
|
|
5512
|
+
if (event.key === "Escape") {
|
|
5513
|
+
event.preventDefault();
|
|
5514
|
+
event.stopPropagation();
|
|
5515
|
+
}
|
|
5516
|
+
dismiss();
|
|
5517
|
+
};
|
|
5518
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
5519
|
+
return () => {
|
|
5520
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
5521
|
+
};
|
|
5522
|
+
}, [
|
|
5523
|
+
dismiss,
|
|
5524
|
+
openOnClick,
|
|
5525
|
+
visible
|
|
5526
|
+
]);
|
|
5527
|
+
const content = visible && !suppressed && jsxs("span", {
|
|
5528
|
+
ref: bubble,
|
|
5529
|
+
id: openOnClick ? id : void 0,
|
|
5530
|
+
className: css$19.bubble,
|
|
5531
|
+
"data-side": side,
|
|
5532
|
+
"data-portal": portal || void 0,
|
|
5533
|
+
"data-pinned": pinned || void 0,
|
|
5534
|
+
"data-align": align,
|
|
5535
|
+
"data-has-shortcut": shortcutKeys?.length ? true : void 0,
|
|
5536
|
+
style: {
|
|
5537
|
+
left: pos.left,
|
|
5538
|
+
top: pos.top,
|
|
5539
|
+
visibility: "hidden",
|
|
5540
|
+
...maxWidth === void 0 ? {} : { maxWidth: `min(${maxWidth}px, calc(100vw - 24px))` }
|
|
5541
|
+
},
|
|
5542
|
+
role: "tooltip",
|
|
5543
|
+
"aria-label": shortcutKeys?.length ? [resolvedLabel, shortcutKeys.join(" ")].filter(Boolean).join(" ") : void 0,
|
|
5544
|
+
children: [resolvedLabel && jsx("span", {
|
|
5545
|
+
className: css$19.label,
|
|
5546
|
+
children: resolvedLabel
|
|
5547
|
+
}), shortcutKeys !== void 0 && shortcutKeys.length > 0 && jsx(ShortcutKeys, {
|
|
5548
|
+
keys: shortcutKeys,
|
|
5549
|
+
variant: "tooltip"
|
|
5550
|
+
})]
|
|
5551
|
+
});
|
|
5552
|
+
return jsxs(TooltipSuppression.Provider, {
|
|
5553
|
+
value: setSuppressed,
|
|
5554
|
+
children: [cloneElement(children, {
|
|
5555
|
+
ref: mergedRef,
|
|
5556
|
+
"aria-describedby": openOnClick && visible ? [children.props["aria-describedby"], id].filter(Boolean).join(" ") : children.props["aria-describedby"],
|
|
5557
|
+
onPointerEnter: (e) => {
|
|
5558
|
+
children.props.onPointerEnter?.(e);
|
|
5559
|
+
if (e.pointerType === "touch") return;
|
|
5560
|
+
if (e.pointerType === "mouse" && !window.matchMedia("(any-hover: hover)").matches) return;
|
|
5561
|
+
triggers.current.hover = true;
|
|
5562
|
+
showAfterDelay(delayMs);
|
|
5563
|
+
},
|
|
5564
|
+
onPointerLeave: (e) => {
|
|
5565
|
+
children.props.onPointerLeave?.(e);
|
|
5566
|
+
triggers.current.hover = false;
|
|
5567
|
+
cancelShow();
|
|
5568
|
+
if (!pinned) withdraw();
|
|
5569
|
+
},
|
|
5570
|
+
onPointerDown: (e) => {
|
|
5571
|
+
children.props.onPointerDown?.(e);
|
|
5572
|
+
if (e.pointerType === "touch" && !pinned) dismiss();
|
|
5573
|
+
},
|
|
5574
|
+
onClick: (e) => {
|
|
5575
|
+
children.props.onClick?.(e);
|
|
5576
|
+
triggers.current.focus = false;
|
|
5577
|
+
cancelShow();
|
|
5578
|
+
if (openOnClick && !disabled && !pinned) {
|
|
5579
|
+
setPinned(true);
|
|
5580
|
+
show();
|
|
5581
|
+
} else withdraw();
|
|
5582
|
+
},
|
|
5583
|
+
onFocus: (e) => {
|
|
5584
|
+
children.props.onFocus?.(e);
|
|
5585
|
+
if (pointerModality()) return;
|
|
5586
|
+
triggers.current.focus = true;
|
|
5587
|
+
showAfterDelay(focusDelayMs);
|
|
5588
|
+
},
|
|
5589
|
+
onBlur: (e) => {
|
|
5590
|
+
children.props.onBlur?.(e);
|
|
5591
|
+
triggers.current.focus = false;
|
|
5592
|
+
hide();
|
|
5593
|
+
}
|
|
5594
|
+
}), portal ? content !== false && createPortal(content, document.body) : content]
|
|
5595
|
+
});
|
|
5596
|
+
}
|
|
5597
|
+
//#endregion
|
|
5598
|
+
//#region lib/types/Menu.js
|
|
5599
|
+
/**
|
|
5600
|
+
* Render one `role="menuitem"` row for a {@link Menu} whose rows are
|
|
5601
|
+
* components rather than `items` data: the same markup and styling as a data
|
|
5602
|
+
* row, so it joins the list's keyboard walk and post-selection focus return
|
|
5603
|
+
* without any shared state. Closing the menu stays the owner's decision, as
|
|
5604
|
+
* it is for data rows.
|
|
5605
|
+
* @param props.children - visible row label.
|
|
5606
|
+
* @param props.shortcut - effective key labels and accessible combination.
|
|
5607
|
+
* @param props.icon - optional leading icon.
|
|
5608
|
+
* @param props.current - whether the row identifies the currently displayed page or view.
|
|
5609
|
+
* @param props.disabled - whether the row cannot be activated.
|
|
5610
|
+
* @param props.disabledReason - why a disabled row cannot be activated, shown in a tooltip and read as its description.
|
|
5611
|
+
* @param props.danger - whether to use the destructive row colors.
|
|
5612
|
+
* @param props.separatorBefore - whether this row starts a new group (hairline above it).
|
|
5613
|
+
* @param props.onSelect - row activation callback.
|
|
5614
|
+
* @returns one menu-item row.
|
|
5615
|
+
*/
|
|
5616
|
+
function MenuItemButton({ children, shortcut, icon, current, disabled = false, disabledReason, danger = false, separatorBefore = false, onSelect }) {
|
|
5617
|
+
const reasonId = useId();
|
|
5618
|
+
const explained = disabled && disabledReason !== void 0;
|
|
5619
|
+
return jsxs("div", {
|
|
5620
|
+
className: css$20.itemWrap,
|
|
5621
|
+
children: [
|
|
5622
|
+
separatorBefore && jsx("div", {
|
|
5623
|
+
className: css$20.separator,
|
|
5624
|
+
role: "separator"
|
|
5625
|
+
}),
|
|
5626
|
+
jsx(Tooltip, {
|
|
5627
|
+
label: disabledReason ?? "",
|
|
5628
|
+
disabled: !explained,
|
|
5629
|
+
side: "bottom",
|
|
5630
|
+
delayMs: 500,
|
|
5631
|
+
maxWidth: 300,
|
|
5632
|
+
portal: true,
|
|
5633
|
+
children: jsxs("button", {
|
|
5634
|
+
type: "button",
|
|
5635
|
+
role: "menuitem",
|
|
5636
|
+
className: clsx(css$20.item, danger && css$20.danger),
|
|
5637
|
+
disabled: disabled && !explained,
|
|
5638
|
+
"aria-disabled": explained || void 0,
|
|
5639
|
+
"aria-describedby": explained ? reasonId : void 0,
|
|
5640
|
+
"aria-keyshortcuts": shortcut?.aria,
|
|
5641
|
+
"aria-current": current === true ? "page" : void 0,
|
|
5642
|
+
onClick: explained ? void 0 : onSelect,
|
|
5643
|
+
children: [
|
|
5644
|
+
icon !== void 0 && jsx("span", {
|
|
5645
|
+
className: css$20.itemIcon,
|
|
5646
|
+
children: icon
|
|
5647
|
+
}),
|
|
5648
|
+
jsx("span", {
|
|
5649
|
+
className: css$20.itemLabel,
|
|
5650
|
+
children
|
|
5651
|
+
}),
|
|
5652
|
+
shortcut !== void 0 && jsx("span", {
|
|
5653
|
+
"aria-hidden": "true",
|
|
5654
|
+
className: css$20.shortcut,
|
|
5655
|
+
children: jsx(ShortcutKeys, {
|
|
5656
|
+
keys: shortcut.keys,
|
|
5657
|
+
className: css$20.shortcutKeys
|
|
5658
|
+
})
|
|
5659
|
+
})
|
|
5660
|
+
]
|
|
5661
|
+
})
|
|
5662
|
+
}),
|
|
5663
|
+
explained && jsx("span", {
|
|
5664
|
+
id: reasonId,
|
|
5665
|
+
hidden: true,
|
|
5666
|
+
children: disabledReason
|
|
5667
|
+
})
|
|
5668
|
+
]
|
|
5669
|
+
});
|
|
5670
|
+
}
|
|
5671
|
+
function isSeparator(entry) {
|
|
5672
|
+
return "type" in entry && entry.type === "separator";
|
|
5673
|
+
}
|
|
5674
|
+
function isLabel(entry) {
|
|
5675
|
+
return "type" in entry && entry.type === "label";
|
|
5676
|
+
}
|
|
5677
|
+
/** Mark icon columns within each separator-delimited data group. */
|
|
5678
|
+
function iconColumns(entries, contextual) {
|
|
5679
|
+
const aligned = /* @__PURE__ */ new Set();
|
|
5680
|
+
if (!contextual) return aligned;
|
|
5681
|
+
let group = [];
|
|
5682
|
+
const finish = () => {
|
|
5683
|
+
if (group.some((entry) => !isLabel(entry) && !isSeparator(entry) && entry.icon !== void 0)) for (const entry of group) aligned.add(entry);
|
|
5684
|
+
group = [];
|
|
5685
|
+
};
|
|
5686
|
+
for (const entry of entries) if (isSeparator(entry)) finish();
|
|
5687
|
+
else group.push(entry);
|
|
5688
|
+
finish();
|
|
5689
|
+
return aligned;
|
|
5690
|
+
}
|
|
5691
|
+
/** Unplaced portal list: hidden but laid out at a fixed origin so offsetWidth/offsetHeight are real. */
|
|
5692
|
+
const MEASURE_STYLE = {
|
|
5693
|
+
visibility: "hidden",
|
|
5694
|
+
left: 0,
|
|
5695
|
+
top: 0
|
|
5696
|
+
};
|
|
5697
|
+
/**
|
|
5698
|
+
* Render an anchored dropdown menu. While the list is open its keys mirror the
|
|
5699
|
+
* composer's: Tab settles the focused row — from the trigger, Tab enters the
|
|
5700
|
+
* list instead — and Escape or Shift+Tab close it and return focus to the
|
|
5701
|
+
* anchor's first button, and selecting a row does the same — the rows unmount
|
|
5702
|
+
* with the list. Only a keyboard on the trigger or inside the list is
|
|
5703
|
+
* intercepted; Tab presses elsewhere on the page stay the browser's.
|
|
5704
|
+
* @param props.contextual - cursor positioning, context dismissal, and per-divider icon alignment.
|
|
5705
|
+
* @param props.exiting - retain an inert popup during dismissal or replacement fade.
|
|
5706
|
+
* @param props.returnFocus - original control captured before asynchronous menu preparation.
|
|
5707
|
+
* @param props.autoFocus - focus the first item on open; the arrow keys walk the list either way.
|
|
5708
|
+
* @param props.open - whether the list is showing (owner-controlled).
|
|
5709
|
+
* @param props.anchor - the trigger element (rendered in place).
|
|
5710
|
+
* @param props.items - selectable data rows and optional separators (default none; with no `children` either, the list is empty).
|
|
5711
|
+
* @param props.selectedId - row shown as selected.
|
|
5712
|
+
* @param props.selectedIds - rows shown as selected when a menu contains independent option groups.
|
|
5713
|
+
* @param props.onSelect - data-row activation callback (not called for disabled rows or submenu parents that only open children).
|
|
5714
|
+
* @param props.onClose - invoked on outside click, Escape, or a window blur
|
|
5715
|
+
* that moved focus into an iframe (the only signal a pointerdown inside a
|
|
5716
|
+
* cross-origin iframe leaves).
|
|
5717
|
+
* @param props.align - list alignment against the anchor (default 'start').
|
|
5718
|
+
* @param props.side - open below (`bottom`, default) or above (`top`) the anchor.
|
|
5719
|
+
* @param props.portal - render the list into document.body, fixed-positioned
|
|
5720
|
+
* synchronously (follows movement, resizing, and browser zoom while open)
|
|
5721
|
+
* and clamped inside the visual viewport. Use when an ancestor's overflow clipping would crop the
|
|
5722
|
+
* in-place list; default false keeps the pure-CSS in-place behavior.
|
|
5723
|
+
* @param props.closeOnPointerLeave - close the list once the pointer has left
|
|
5724
|
+
* both trigger and list for the pointer grace (default false keeps it open
|
|
5725
|
+
* until outside click/Escape/selection). The grace makes the 4px trigger->list
|
|
5726
|
+
* gap and a brief overshoot survivable; coming back cancels the close.
|
|
5727
|
+
* @param props.dense - reduce vertical row spacing without changing the standard typography or card width.
|
|
5728
|
+
* @param props.compact - use reduced menu typography and spacing.
|
|
5729
|
+
* @param props.getAnchorRect - portal mode only: supply the anchor rect
|
|
5730
|
+
* directly (e.g. from a host-owned trigger button) instead of measuring the
|
|
5731
|
+
* Menu's own wrapper span. Required when the wrapper isn't itself laid out at
|
|
5732
|
+
* the trigger (render-prop anchors, effect-positioned proxies — measuring the
|
|
5733
|
+
* wrapper there races the host's layout effects). Called while tracking the
|
|
5734
|
+
* anchor and on scroll/resize; return null to defer placement.
|
|
5735
|
+
* @param props.minWidthFromAnchor - in portal mode, keep the menu at least as wide as its anchor, bounded by viewport margins.
|
|
5736
|
+
* @param props.footer - rows pinned below the scrolling items area, separated
|
|
5737
|
+
* by a hairline; contextual menus reuse an explicit leading separator. These rows stay visible while the items above scroll.
|
|
5738
|
+
* @param props.children - component rows rendered after `items` in the same
|
|
5739
|
+
* list, each a `role="menuitem"` button such as {@link MenuItemButton}; they
|
|
5740
|
+
* share the keyboard walk, the submenu exclusivity, and the post-selection
|
|
5741
|
+
* focus return.
|
|
5742
|
+
* @param props.selection - how a selected row is marked: a trailing check
|
|
5743
|
+
* (`'check'`, default — figma .Menu_cell) or the hover fill held on the row
|
|
5744
|
+
* with no check (`'fill'`, for icon-labelled rows where a trailing glyph
|
|
5745
|
+
* crowds the cell).
|
|
5746
|
+
* @param props.className - extra class on the anchor wrapper span.
|
|
5747
|
+
* @param props.listClassName - extra class on the dropdown card itself; the
|
|
5748
|
+
* only style hook that reaches a portaled list, which renders under
|
|
5749
|
+
* document.body outside the owner's DOM subtree.
|
|
5750
|
+
* @returns anchor wrapper with the conditional list.
|
|
5751
|
+
*/
|
|
5752
|
+
function Menu({ open, anchor, items = [], children, selectedId, selectedIds, onSelect, onClose, align = "start", side = "bottom", portal = false, closeOnPointerLeave = false, dense = false, compact = false, autoFocus = false, selection = "check", getAnchorRect, minWidthFromAnchor = false, footer, className, listClassName, contextual = false, exiting, returnFocus }) {
|
|
5753
|
+
const rootRef = useRef(null);
|
|
5754
|
+
const listRef = useRef(null);
|
|
5755
|
+
const menuId = useId();
|
|
5756
|
+
useLayoutEffect(() => {
|
|
5757
|
+
const trigger = returnFocus?.();
|
|
5758
|
+
if (!open || !contextual || trigger == null) return;
|
|
5759
|
+
const previous = trigger.getAttribute("aria-controls");
|
|
5760
|
+
trigger.setAttribute("aria-controls", [previous, menuId].filter(Boolean).join(" "));
|
|
5761
|
+
return () => {
|
|
5762
|
+
if (previous === null) trigger.removeAttribute("aria-controls");
|
|
5763
|
+
else trigger.setAttribute("aria-controls", previous);
|
|
5764
|
+
};
|
|
5765
|
+
}, [
|
|
5766
|
+
open,
|
|
5767
|
+
contextual,
|
|
5768
|
+
returnFocus,
|
|
5769
|
+
menuId
|
|
5770
|
+
]);
|
|
5771
|
+
/** Index the arrow walk last focused, the resume point when focus left the rows. */
|
|
5772
|
+
const walkIndex = useRef(null);
|
|
5773
|
+
/**
|
|
5774
|
+
* The control that had the keyboard when this menu opened — its own trigger,
|
|
5775
|
+
* which an anchor that wraps several controls (a split button) would not be
|
|
5776
|
+
* able to name by position.
|
|
5777
|
+
*/
|
|
5778
|
+
const triggerRef = useRef(null);
|
|
5779
|
+
const selectingWithTab = useRef(false);
|
|
5780
|
+
/**
|
|
5781
|
+
* Hand the keyboard back to the trigger that opened the menu — or, when the
|
|
5782
|
+
* anchor never held it, to the anchor's first button. Focus left on a removed
|
|
5783
|
+
* row otherwise falls to the page body, where the next Tab restarts from the
|
|
5784
|
+
* top of the page.
|
|
5785
|
+
* @param navigation - whether explicit keyboard traversal should retain its focus indicator.
|
|
5786
|
+
*/
|
|
5787
|
+
const refocusAnchor = (navigation = false) => {
|
|
5788
|
+
const trigger = returnFocus?.() ?? triggerRef.current;
|
|
5789
|
+
const target = trigger !== null && document.contains(trigger) && !trigger.disabled ? trigger : rootRef.current?.querySelector("button:not(:disabled)");
|
|
5790
|
+
if (target == null) return;
|
|
5791
|
+
if (navigation) target.focus();
|
|
5792
|
+
else focusWithoutRing(target);
|
|
5793
|
+
};
|
|
5794
|
+
/**
|
|
5795
|
+
* Post-selection focus, for the paths where the rows unmount with the list.
|
|
5796
|
+
* A selection whose owner keeps the menu open is left alone, and so is an
|
|
5797
|
+
* owner that moved focus itself (a presented file card hands it to its
|
|
5798
|
+
* preview button): only a keyboard left on the closing list (or on the body
|
|
5799
|
+
* its removal produced) comes back to the trigger.
|
|
5800
|
+
*/
|
|
5801
|
+
const refocusAfterSelection = () => {
|
|
5802
|
+
const navigation = selectingWithTab.current;
|
|
5803
|
+
queueMicrotask(() => {
|
|
5804
|
+
if (openRef.current && exitingRef.current === void 0) return;
|
|
5805
|
+
const active = document.activeElement;
|
|
5806
|
+
if (active === null || active === document.body || listRef.current?.contains(active) === true) refocusAnchor(navigation);
|
|
5807
|
+
});
|
|
5808
|
+
};
|
|
5809
|
+
const exitingRef = useRef(exiting);
|
|
5810
|
+
exitingRef.current = exiting;
|
|
5811
|
+
const openRef = useRef(open);
|
|
5812
|
+
openRef.current = open;
|
|
5813
|
+
const [openSubmenuId, setOpenSubmenuId] = useState(null);
|
|
5814
|
+
const close = useRef(onClose);
|
|
5815
|
+
close.current = onClose;
|
|
5816
|
+
const lastAnchorRect = useRef(null);
|
|
5817
|
+
const readAnchor = useCallback(() => {
|
|
5818
|
+
if ((returnFocus?.() ?? rootRef.current)?.closest("[inert], [hidden]") != null) {
|
|
5819
|
+
if (exitingRef.current === void 0) close.current();
|
|
5820
|
+
return lastAnchorRect.current;
|
|
5821
|
+
}
|
|
5822
|
+
lastAnchorRect.current = getAnchorRect === void 0 ? rootRef.current?.getBoundingClientRect() ?? null : getAnchorRect();
|
|
5823
|
+
return lastAnchorRect.current;
|
|
5824
|
+
}, [getAnchorRect, returnFocus]);
|
|
5825
|
+
const fixedPos = useAnchoredPosition({
|
|
5826
|
+
open: open && portal,
|
|
5827
|
+
anchorRef: rootRef,
|
|
5828
|
+
panelRef: listRef,
|
|
5829
|
+
side,
|
|
5830
|
+
align,
|
|
5831
|
+
gap: 4,
|
|
5832
|
+
margin: 12,
|
|
5833
|
+
frameClearance: !contextual,
|
|
5834
|
+
minWidthFromAnchor,
|
|
5835
|
+
getAnchorRect: readAnchor
|
|
5836
|
+
});
|
|
5837
|
+
const positioned = !portal || fixedPos !== null;
|
|
5838
|
+
const { arm: armClose, cancel: cancelClose } = usePointerGrace(onClose);
|
|
5839
|
+
useEffect(() => {
|
|
5840
|
+
if (!open) return;
|
|
5841
|
+
const active = document.activeElement;
|
|
5842
|
+
triggerRef.current = active instanceof HTMLElement && (getAnchorRect !== void 0 || rootRef.current?.contains(active) === true) ? active : null;
|
|
5843
|
+
}, [open]);
|
|
5844
|
+
useEffect(() => {
|
|
5845
|
+
if (!open || exiting !== void 0 || !autoFocus || !positioned || listRef.current?.contains(document.activeElement)) return;
|
|
5846
|
+
const first = listRef.current?.querySelector("button:not(:disabled)");
|
|
5847
|
+
walkIndex.current = first === void 0 || first === null ? null : 0;
|
|
5848
|
+
if (first != null) focusWithoutRing(first);
|
|
5849
|
+
}, [
|
|
5850
|
+
open,
|
|
5851
|
+
autoFocus,
|
|
5852
|
+
items.length,
|
|
5853
|
+
footer?.length,
|
|
5854
|
+
positioned,
|
|
5855
|
+
exiting
|
|
5856
|
+
]);
|
|
5857
|
+
useEffect(() => {
|
|
5858
|
+
if (!open || exiting !== void 0) {
|
|
5859
|
+
setOpenSubmenuId(null);
|
|
5860
|
+
walkIndex.current = null;
|
|
5861
|
+
return;
|
|
5862
|
+
}
|
|
5863
|
+
const composition = observeComposition(document);
|
|
5864
|
+
const onPointerDown = (e) => {
|
|
5865
|
+
if (!(e.target instanceof Node) || contextual && e.button === 2) return;
|
|
5866
|
+
if (rootRef.current?.contains(e.target) === true) return;
|
|
5867
|
+
if (listRef.current?.contains(e.target) === true) return;
|
|
5868
|
+
onClose();
|
|
5869
|
+
};
|
|
5870
|
+
const onKeyDown = (e) => {
|
|
5871
|
+
if (composition.guards(e) || isBehindModal(triggerRef.current ?? rootRef.current) || e.defaultPrevented || e.ctrlKey || e.altKey || e.metaKey) return;
|
|
5872
|
+
const focused = document.activeElement;
|
|
5873
|
+
const insideList = listRef.current?.contains(focused) === true;
|
|
5874
|
+
const anchored = focused === returnFocus?.() || rootRef.current?.contains(focused) === true || insideList;
|
|
5875
|
+
if (e.key === "Escape" && !e.shiftKey) {
|
|
5876
|
+
e.preventDefault();
|
|
5877
|
+
if (e.repeat) return;
|
|
5878
|
+
onClose();
|
|
5879
|
+
if (anchored || autoFocus) refocusAnchor();
|
|
5640
5880
|
}
|
|
5641
|
-
|
|
5881
|
+
if (e.key === "Tab") {
|
|
5882
|
+
const list = listRef.current;
|
|
5883
|
+
if (list === null || !anchored) return;
|
|
5884
|
+
if (e.shiftKey) {
|
|
5885
|
+
e.preventDefault();
|
|
5886
|
+
onClose();
|
|
5887
|
+
refocusAnchor(true);
|
|
5888
|
+
return;
|
|
5889
|
+
}
|
|
5890
|
+
if (insideList) {
|
|
5891
|
+
if (focused instanceof Element && focused.getAttribute("role") === "menuitem") {
|
|
5892
|
+
e.preventDefault();
|
|
5893
|
+
if (focused.getAttribute("aria-disabled") === "true") {
|
|
5894
|
+
onClose();
|
|
5895
|
+
refocusAnchor(true);
|
|
5896
|
+
return;
|
|
5897
|
+
}
|
|
5898
|
+
selectingWithTab.current = true;
|
|
5899
|
+
try {
|
|
5900
|
+
focused.click();
|
|
5901
|
+
} finally {
|
|
5902
|
+
selectingWithTab.current = false;
|
|
5903
|
+
}
|
|
5904
|
+
}
|
|
5905
|
+
return;
|
|
5906
|
+
}
|
|
5907
|
+
const row = list.querySelector("button:not(:disabled)");
|
|
5908
|
+
if (row === null) return;
|
|
5909
|
+
e.preventDefault();
|
|
5910
|
+
row.focus();
|
|
5911
|
+
walkIndex.current = 0;
|
|
5912
|
+
return;
|
|
5913
|
+
}
|
|
5914
|
+
if (![
|
|
5915
|
+
"ArrowDown",
|
|
5916
|
+
"ArrowUp",
|
|
5917
|
+
"Home",
|
|
5918
|
+
"End"
|
|
5919
|
+
].includes(e.key)) return;
|
|
5920
|
+
const list = listRef.current;
|
|
5921
|
+
if (list === null || !anchored) return;
|
|
5922
|
+
const buttons = Array.from(list.querySelectorAll("button:not(:disabled)"));
|
|
5923
|
+
if (buttons.length === 0) return;
|
|
5924
|
+
const index = buttons.indexOf(focused);
|
|
5925
|
+
const from = index >= 0 ? index : walkIndex.current;
|
|
5926
|
+
const next = e.key === "Home" ? 0 : e.key === "End" ? buttons.length - 1 : from === null ? e.key === "ArrowDown" ? 0 : buttons.length - 1 : (from + (e.key === "ArrowDown" ? 1 : -1) + buttons.length) % buttons.length;
|
|
5927
|
+
e.preventDefault();
|
|
5928
|
+
walkIndex.current = next;
|
|
5929
|
+
buttons[next]?.focus();
|
|
5642
5930
|
};
|
|
5643
|
-
|
|
5931
|
+
const onWindowBlur = () => {
|
|
5932
|
+
if (document.activeElement instanceof HTMLIFrameElement) onClose();
|
|
5933
|
+
};
|
|
5934
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
5935
|
+
const onEscape = (event) => {
|
|
5936
|
+
if (event.key === "Escape") onKeyDown(event);
|
|
5937
|
+
};
|
|
5938
|
+
const onOtherKey = (event) => {
|
|
5939
|
+
if (event.key !== "Escape") onKeyDown(event);
|
|
5940
|
+
};
|
|
5941
|
+
document.addEventListener("keydown", onOtherKey);
|
|
5942
|
+
document.addEventListener("keydown", onEscape, true);
|
|
5943
|
+
window.addEventListener("blur", onWindowBlur);
|
|
5644
5944
|
return () => {
|
|
5645
|
-
|
|
5945
|
+
composition.dispose();
|
|
5946
|
+
document.removeEventListener("pointerdown", onPointerDown);
|
|
5947
|
+
document.removeEventListener("keydown", onOtherKey);
|
|
5948
|
+
document.removeEventListener("keydown", onEscape, true);
|
|
5949
|
+
window.removeEventListener("blur", onWindowBlur);
|
|
5646
5950
|
};
|
|
5647
5951
|
}, [
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5952
|
+
open,
|
|
5953
|
+
onClose,
|
|
5954
|
+
autoFocus,
|
|
5955
|
+
contextual,
|
|
5956
|
+
exiting
|
|
5651
5957
|
]);
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5958
|
+
useLayoutEffect(() => {
|
|
5959
|
+
if (exiting === "dismiss" && listRef.current?.contains(document.activeElement)) refocusAnchor();
|
|
5960
|
+
}, [exiting]);
|
|
5961
|
+
useEffect(() => {
|
|
5962
|
+
if (!open) cancelClose();
|
|
5963
|
+
}, [open, cancelClose]);
|
|
5964
|
+
const scrollable = !items.some((entry) => !isSeparator(entry) && !isLabel(entry) && entry.submenu !== void 0 && entry.submenu.length > 0);
|
|
5965
|
+
const itemColumns = iconColumns(items, contextual);
|
|
5966
|
+
const footerColumns = iconColumns(footer ?? [], contextual);
|
|
5967
|
+
const renderEntry = (entry, columns) => {
|
|
5968
|
+
if (isSeparator(entry)) return jsx("div", {
|
|
5969
|
+
className: css$20.separator,
|
|
5970
|
+
role: "separator"
|
|
5971
|
+
}, entry.id);
|
|
5972
|
+
if (isLabel(entry)) return jsx("div", {
|
|
5973
|
+
className: css$20.label,
|
|
5974
|
+
role: "presentation",
|
|
5975
|
+
children: entry.text
|
|
5976
|
+
}, entry.id);
|
|
5977
|
+
const hasSub = entry.submenu !== void 0 && entry.submenu.length > 0;
|
|
5978
|
+
const subOpen = hasSub && openSubmenuId === entry.id;
|
|
5979
|
+
const subColumns = iconColumns(entry.submenu ?? [], contextual);
|
|
5980
|
+
const selected = entry.id === selectedId || selectedIds?.includes(entry.id) === true;
|
|
5981
|
+
return jsxs("div", {
|
|
5982
|
+
className: css$20.itemWrap,
|
|
5983
|
+
onMouseEnter: hasSub ? () => {
|
|
5984
|
+
setOpenSubmenuId(entry.id);
|
|
5985
|
+
} : void 0,
|
|
5986
|
+
onMouseLeave: () => {
|
|
5987
|
+
setOpenSubmenuId(null);
|
|
5988
|
+
},
|
|
5989
|
+
children: [jsxs("button", {
|
|
5990
|
+
type: "button",
|
|
5991
|
+
role: "menuitem",
|
|
5992
|
+
className: clsx(css$20.item, selected && (selection === "fill" ? css$20.selectedFill : css$20.selected), entry.danger === true && css$20.danger),
|
|
5993
|
+
disabled: entry.disabled,
|
|
5994
|
+
"aria-keyshortcuts": entry.shortcut?.aria,
|
|
5995
|
+
"aria-haspopup": hasSub ? "menu" : void 0,
|
|
5996
|
+
"aria-expanded": hasSub ? subOpen : void 0,
|
|
5997
|
+
onFocus: hasSub ? () => {
|
|
5998
|
+
setOpenSubmenuId(entry.id);
|
|
5999
|
+
} : void 0,
|
|
6000
|
+
onClick: () => {
|
|
6001
|
+
if (hasSub) {
|
|
6002
|
+
setOpenSubmenuId(entry.id);
|
|
6003
|
+
return;
|
|
6004
|
+
}
|
|
6005
|
+
onSelect?.(entry.id);
|
|
6006
|
+
},
|
|
6007
|
+
children: [
|
|
6008
|
+
(entry.icon !== void 0 || columns.has(entry)) && jsx("span", {
|
|
6009
|
+
"aria-hidden": "true",
|
|
6010
|
+
className: css$20.itemIcon,
|
|
6011
|
+
children: entry.icon
|
|
6012
|
+
}),
|
|
6013
|
+
jsx("span", {
|
|
6014
|
+
className: css$20.itemLabel,
|
|
6015
|
+
children: entry.label
|
|
6016
|
+
}),
|
|
6017
|
+
entry.shortcut !== void 0 && jsx("span", {
|
|
6018
|
+
"aria-hidden": "true",
|
|
6019
|
+
className: css$20.shortcut,
|
|
6020
|
+
children: jsx(ShortcutKeys, {
|
|
6021
|
+
keys: entry.shortcut.keys,
|
|
6022
|
+
className: css$20.shortcutKeys
|
|
6023
|
+
})
|
|
6024
|
+
}),
|
|
6025
|
+
selected && selection === "check" && jsx(IconCheckOutlineRegular, { className: css$20.check })
|
|
6026
|
+
]
|
|
6027
|
+
}), subOpen && entry.submenu !== void 0 && jsx(MenuSurface, {
|
|
6028
|
+
compact,
|
|
6029
|
+
className: clsx(css$20.submenu, compact && css$20.compactList),
|
|
6030
|
+
role: "menu",
|
|
6031
|
+
children: entry.submenu.map((sub) => isSeparator(sub) ? jsx("div", {
|
|
6032
|
+
className: css$20.separator,
|
|
6033
|
+
role: "separator"
|
|
6034
|
+
}, sub.id) : isLabel(sub) ? jsx("div", {
|
|
6035
|
+
className: css$20.label,
|
|
6036
|
+
children: sub.text
|
|
6037
|
+
}, sub.id) : jsxs("button", {
|
|
6038
|
+
type: "button",
|
|
6039
|
+
role: "menuitem",
|
|
6040
|
+
className: css$20.item,
|
|
6041
|
+
disabled: sub.disabled,
|
|
6042
|
+
"aria-keyshortcuts": sub.shortcut?.aria,
|
|
6043
|
+
onClick: () => {
|
|
6044
|
+
onSelect?.(sub.id);
|
|
6045
|
+
refocusAfterSelection();
|
|
6046
|
+
},
|
|
6047
|
+
children: [
|
|
6048
|
+
(sub.icon !== void 0 || subColumns.has(sub)) && jsx("span", {
|
|
6049
|
+
"aria-hidden": "true",
|
|
6050
|
+
className: css$20.itemIcon,
|
|
6051
|
+
children: sub.icon
|
|
6052
|
+
}),
|
|
6053
|
+
jsx("span", {
|
|
6054
|
+
className: css$20.itemLabel,
|
|
6055
|
+
children: sub.label
|
|
6056
|
+
}),
|
|
6057
|
+
sub.shortcut !== void 0 && jsx("span", {
|
|
6058
|
+
"aria-hidden": "true",
|
|
6059
|
+
className: css$20.shortcut,
|
|
6060
|
+
children: jsx(ShortcutKeys, {
|
|
6061
|
+
keys: sub.shortcut.keys,
|
|
6062
|
+
className: css$20.shortcutKeys
|
|
6063
|
+
})
|
|
6064
|
+
})
|
|
6065
|
+
]
|
|
6066
|
+
}, sub.id))
|
|
6067
|
+
})]
|
|
6068
|
+
}, entry.id);
|
|
6069
|
+
};
|
|
6070
|
+
const collapseSubmenuFrom = (e) => {
|
|
6071
|
+
const row = e.target instanceof Element ? e.target.closest("button[role=\"menuitem\"]") : null;
|
|
6072
|
+
if (row === null || row.getAttribute("aria-haspopup") === "menu") return;
|
|
6073
|
+
if (row.closest("[role=\"menu\"]") !== e.currentTarget) return;
|
|
6074
|
+
setOpenSubmenuId(null);
|
|
6075
|
+
};
|
|
6076
|
+
const list = open && jsxs(MenuSurface, {
|
|
6077
|
+
compact,
|
|
6078
|
+
id: contextual ? menuId : void 0,
|
|
6079
|
+
ref: listRef,
|
|
6080
|
+
className: clsx(css$20.list, listClassName, dense && css$20.denseList, compact && css$20.compactList, scrollable && css$20.scrollable, portal && css$20.portal, side === "top" && !portal && css$20.sideTop, align === "end" && !portal && css$20.alignEnd),
|
|
6081
|
+
style: portal ? fixedPos ?? MEASURE_STYLE : void 0,
|
|
6082
|
+
role: exiting === void 0 ? "menu" : void 0,
|
|
6083
|
+
"aria-hidden": exiting === void 0 ? void 0 : true,
|
|
6084
|
+
"data-context-menu": contextual ? "" : void 0,
|
|
6085
|
+
phase: contextual ? exiting ?? "enter" : void 0,
|
|
6086
|
+
onClick: (e) => {
|
|
6087
|
+
e.stopPropagation();
|
|
6088
|
+
const row = e.target instanceof Element ? e.target.closest("button[role=\"menuitem\"]") : null;
|
|
6089
|
+
if (row !== null && row.getAttribute("aria-haspopup") !== "menu") refocusAfterSelection();
|
|
5666
6090
|
},
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
6091
|
+
onContextMenu: contextual ? (event) => {
|
|
6092
|
+
event.preventDefault();
|
|
6093
|
+
event.stopPropagation();
|
|
6094
|
+
onClose();
|
|
6095
|
+
} : void 0,
|
|
6096
|
+
onMouseOver: collapseSubmenuFrom,
|
|
6097
|
+
onFocus: collapseSubmenuFrom,
|
|
6098
|
+
children: [jsxs("div", {
|
|
6099
|
+
className: css$20.viewport,
|
|
6100
|
+
role: "presentation",
|
|
6101
|
+
children: [items.map((entry) => renderEntry(entry, itemColumns)), children]
|
|
6102
|
+
}), footer !== void 0 && footer.length > 0 && jsx("div", {
|
|
6103
|
+
className: css$20.footer,
|
|
6104
|
+
role: "presentation",
|
|
6105
|
+
children: footer.map((entry) => renderEntry(entry, footerColumns))
|
|
5675
6106
|
})]
|
|
5676
6107
|
});
|
|
5677
|
-
return jsxs(
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
triggers.current.hover = true;
|
|
5686
|
-
showAfterDelay(delayMs);
|
|
5687
|
-
},
|
|
5688
|
-
onMouseLeave: (e) => {
|
|
5689
|
-
children.props.onMouseLeave?.(e);
|
|
5690
|
-
triggers.current.hover = false;
|
|
5691
|
-
cancelShow();
|
|
5692
|
-
if (!pinned) withdraw();
|
|
5693
|
-
},
|
|
5694
|
-
onClick: (e) => {
|
|
5695
|
-
children.props.onClick?.(e);
|
|
5696
|
-
trackAnchor(e);
|
|
5697
|
-
triggers.current.focus = false;
|
|
5698
|
-
cancelShow();
|
|
5699
|
-
if (openOnClick && !disabled && !pinned) {
|
|
5700
|
-
setPinned(true);
|
|
5701
|
-
show();
|
|
5702
|
-
} else withdraw();
|
|
5703
|
-
},
|
|
5704
|
-
onFocus: (e) => {
|
|
5705
|
-
children.props.onFocus?.(e);
|
|
5706
|
-
trackAnchor(e);
|
|
5707
|
-
if (pointerModality()) return;
|
|
5708
|
-
triggers.current.focus = true;
|
|
5709
|
-
showAfterDelay(focusDelayMs);
|
|
5710
|
-
},
|
|
5711
|
-
onBlur: (e) => {
|
|
5712
|
-
children.props.onBlur?.(e);
|
|
5713
|
-
triggers.current.focus = false;
|
|
5714
|
-
hide();
|
|
5715
|
-
}
|
|
5716
|
-
}), portal ? content !== false && createPortal(content, document.body) : content]
|
|
6108
|
+
return jsxs("span", {
|
|
6109
|
+
ref: rootRef,
|
|
6110
|
+
className: clsx(css$20.root, className),
|
|
6111
|
+
onPointerEnter: closeOnPointerLeave ? cancelClose : void 0,
|
|
6112
|
+
onPointerLeave: closeOnPointerLeave ? () => {
|
|
6113
|
+
if (open) armClose();
|
|
6114
|
+
} : void 0,
|
|
6115
|
+
children: [anchor, portal ? list !== false && createPortal(list, document.body) : list]
|
|
5717
6116
|
});
|
|
5718
6117
|
}
|
|
5719
6118
|
//#endregion
|
|
6119
|
+
//#region lib/types/useAnchoredMaxHeight.js
|
|
6120
|
+
/**
|
|
6121
|
+
* Viewport-fit hook for bottom-anchored overlays (slash menu, popupSelect):
|
|
6122
|
+
* the element's bottom edge is laid out independent of its height, so it
|
|
6123
|
+
* grows upward and only the top edge can collide with the viewport — clamp
|
|
6124
|
+
* the design cap to the space between that edge and the viewport top.
|
|
6125
|
+
*/
|
|
6126
|
+
/** Safe distance kept between the overlay and the viewport top edge (mirrors the Menu portal margin). */
|
|
6127
|
+
const MARGIN = 12;
|
|
6128
|
+
/**
|
|
6129
|
+
* Clamp a bottom-anchored overlay's max-height to the viewport.
|
|
6130
|
+
* @param ref - the overlay element; a null current (overlay closed) skips measuring.
|
|
6131
|
+
* @param cap - design max-height in px (the clamp never exceeds it).
|
|
6132
|
+
* @param signal - re-measure trigger: pass the overlay's render state so anchor
|
|
6133
|
+
* moves (composer growth) re-fit; resize/scroll re-fit while mounted.
|
|
6134
|
+
* @returns the max-height to apply inline, in px.
|
|
6135
|
+
*/
|
|
6136
|
+
function useAnchoredMaxHeight(ref, cap, signal) {
|
|
6137
|
+
const [maxHeight, setMaxHeight] = useState(cap);
|
|
6138
|
+
useLayoutEffect(() => {
|
|
6139
|
+
const el = ref.current;
|
|
6140
|
+
if (el === null) return;
|
|
6141
|
+
const fit = () => {
|
|
6142
|
+
setMaxHeight(Math.min(cap, Math.max(0, el.getBoundingClientRect().bottom - MARGIN)));
|
|
6143
|
+
};
|
|
6144
|
+
fit();
|
|
6145
|
+
window.addEventListener("resize", fit);
|
|
6146
|
+
window.addEventListener("scroll", fit, true);
|
|
6147
|
+
return () => {
|
|
6148
|
+
window.removeEventListener("resize", fit);
|
|
6149
|
+
window.removeEventListener("scroll", fit, true);
|
|
6150
|
+
};
|
|
6151
|
+
}, [
|
|
6152
|
+
ref,
|
|
6153
|
+
cap,
|
|
6154
|
+
signal
|
|
6155
|
+
]);
|
|
6156
|
+
return maxHeight;
|
|
6157
|
+
}
|
|
6158
|
+
//#endregion
|
|
6159
|
+
//#region lib/types/clipboard.js
|
|
6160
|
+
/**
|
|
6161
|
+
* Write text to the host clipboard, preferring the async Clipboard API and
|
|
6162
|
+
* falling back to `execCommand('copy')` on hosts (jsdom, insecure contexts)
|
|
6163
|
+
* that omit it.
|
|
6164
|
+
* @param text - the exact text to place on the clipboard.
|
|
6165
|
+
* @returns true only when the host accepted the write.
|
|
6166
|
+
*/
|
|
6167
|
+
async function writeClipboard(text) {
|
|
6168
|
+
if (navigator.clipboard?.writeText) try {
|
|
6169
|
+
await navigator.clipboard.writeText(text);
|
|
6170
|
+
return true;
|
|
6171
|
+
} catch {
|
|
6172
|
+
return false;
|
|
6173
|
+
}
|
|
6174
|
+
const exec = typeof document.execCommand === "function" ? document.execCommand.bind(document) : void 0;
|
|
6175
|
+
if (exec === void 0) return false;
|
|
6176
|
+
const el = document.createElement("textarea");
|
|
6177
|
+
el.value = text;
|
|
6178
|
+
el.setAttribute("readonly", "");
|
|
6179
|
+
el.style.position = "fixed";
|
|
6180
|
+
el.style.left = "-9999px";
|
|
6181
|
+
document.body.appendChild(el);
|
|
6182
|
+
el.select();
|
|
6183
|
+
try {
|
|
6184
|
+
return exec("copy");
|
|
6185
|
+
} catch {
|
|
6186
|
+
return false;
|
|
6187
|
+
} finally {
|
|
6188
|
+
el.remove();
|
|
6189
|
+
}
|
|
6190
|
+
}
|
|
6191
|
+
//#endregion
|
|
5720
6192
|
//#region lib/types/HoverCard.js
|
|
5721
6193
|
const INLINE_PREVIEW_WIDTH = 300;
|
|
5722
6194
|
const ANCHOR_GAP = 8;
|
|
@@ -5883,7 +6355,7 @@ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyT
|
|
|
5883
6355
|
const copyable = copyText !== void 0;
|
|
5884
6356
|
const card = open && pos !== null && !suppressed && jsx("div", {
|
|
5885
6357
|
ref: cardRef,
|
|
5886
|
-
className: clsx(css$
|
|
6358
|
+
className: clsx(css$21.card, inline && css$21.media, copyable && css$21.copyable, copied && css$21.feedback),
|
|
5887
6359
|
style: {
|
|
5888
6360
|
...pos,
|
|
5889
6361
|
minHeight: copied && copyHeightRef.current !== null ? copyHeightRef.current : void 0
|
|
@@ -5904,14 +6376,14 @@ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyT
|
|
|
5904
6376
|
copy(copyText);
|
|
5905
6377
|
} : void 0,
|
|
5906
6378
|
children: copied ? jsx("span", {
|
|
5907
|
-
className: css$
|
|
6379
|
+
className: css$21.copied,
|
|
5908
6380
|
"aria-hidden": "true",
|
|
5909
6381
|
children: copiedLabel
|
|
5910
6382
|
}) : content
|
|
5911
6383
|
});
|
|
5912
6384
|
return jsxs("span", {
|
|
5913
6385
|
ref: rootRef,
|
|
5914
|
-
className: clsx(css$
|
|
6386
|
+
className: clsx(css$21.root, inline && css$21.inline),
|
|
5915
6387
|
onFocus: inline ? (event) => {
|
|
5916
6388
|
if (!disabled && event.target.matches(":focus-visible")) {
|
|
5917
6389
|
cancelClose();
|
|
@@ -5950,7 +6422,7 @@ function HoverCard({ anchor, content, openDelayMs = 500, disabled = false, copyT
|
|
|
5950
6422
|
children: anchor
|
|
5951
6423
|
}),
|
|
5952
6424
|
open && !suppressed && copyable && jsx("span", {
|
|
5953
|
-
className: css$
|
|
6425
|
+
className: css$21.status,
|
|
5954
6426
|
role: "status",
|
|
5955
6427
|
children: copied ? copiedLabel : ""
|
|
5956
6428
|
}),
|
|
@@ -5985,11 +6457,11 @@ function Modal({ open, onClose, title, closeLabel, description, children, footer
|
|
|
5985
6457
|
useModalLayer(dialog, open, onClose);
|
|
5986
6458
|
if (!open) return null;
|
|
5987
6459
|
return createPortal(jsxs("div", {
|
|
5988
|
-
className: css$
|
|
6460
|
+
className: css$22.root,
|
|
5989
6461
|
role: "presentation",
|
|
5990
6462
|
onKeyDownCapture,
|
|
5991
6463
|
children: [jsx("div", {
|
|
5992
|
-
className: css$
|
|
6464
|
+
className: css$22.mask,
|
|
5993
6465
|
style: backdropBlur ? void 0 : { backdropFilter: "none" },
|
|
5994
6466
|
"aria-hidden": "true",
|
|
5995
6467
|
onClick: onClose
|
|
@@ -5997,37 +6469,37 @@ function Modal({ open, onClose, title, closeLabel, description, children, footer
|
|
|
5997
6469
|
ref: dialog,
|
|
5998
6470
|
tabIndex: -1,
|
|
5999
6471
|
"data-shortcut-modal": shortcutModal,
|
|
6000
|
-
className: clsx(css$
|
|
6472
|
+
className: clsx(css$22.dialog, className),
|
|
6001
6473
|
role: "dialog",
|
|
6002
6474
|
"aria-modal": "true",
|
|
6003
6475
|
"aria-label": title,
|
|
6004
6476
|
children: headless ? children : jsxs(Fragment, { children: [jsxs("div", {
|
|
6005
|
-
className: clsx(css$
|
|
6477
|
+
className: clsx(css$22.content, contentClassName),
|
|
6006
6478
|
children: [
|
|
6007
6479
|
jsxs("div", {
|
|
6008
|
-
className: css$
|
|
6480
|
+
className: css$22.header,
|
|
6009
6481
|
children: [jsx("h2", {
|
|
6010
|
-
className: css$
|
|
6482
|
+
className: css$22.title,
|
|
6011
6483
|
children: title
|
|
6012
6484
|
}), jsx("button", {
|
|
6013
6485
|
type: "button",
|
|
6014
|
-
className: css$
|
|
6486
|
+
className: css$22.close,
|
|
6015
6487
|
"aria-label": closeLabel,
|
|
6016
6488
|
onClick: onClose,
|
|
6017
6489
|
children: jsx(IconCloseOutlineRegular, { size: 14 })
|
|
6018
6490
|
})]
|
|
6019
6491
|
}),
|
|
6020
6492
|
description !== void 0 && description !== "" && jsx("p", {
|
|
6021
|
-
className: css$
|
|
6493
|
+
className: css$22.description,
|
|
6022
6494
|
children: description
|
|
6023
6495
|
}),
|
|
6024
6496
|
children !== void 0 && jsx("div", {
|
|
6025
|
-
className: css$
|
|
6497
|
+
className: css$22.body,
|
|
6026
6498
|
children
|
|
6027
6499
|
})
|
|
6028
6500
|
]
|
|
6029
6501
|
}), footer !== void 0 && jsx("div", {
|
|
6030
|
-
className: css$
|
|
6502
|
+
className: css$22.footer,
|
|
6031
6503
|
children: footer
|
|
6032
6504
|
})] })
|
|
6033
6505
|
})]
|
|
@@ -6051,13 +6523,13 @@ function OnboardingSurface({ children }) {
|
|
|
6051
6523
|
};
|
|
6052
6524
|
}, []);
|
|
6053
6525
|
return createPortal(jsxs("div", {
|
|
6054
|
-
className: css$
|
|
6526
|
+
className: css$23.onboardingOverlay,
|
|
6055
6527
|
role: "presentation",
|
|
6056
6528
|
children: [jsx("div", {
|
|
6057
|
-
className: css$
|
|
6529
|
+
className: css$23.onboardingMask,
|
|
6058
6530
|
"aria-hidden": "true"
|
|
6059
6531
|
}), jsx("div", {
|
|
6060
|
-
className: css$
|
|
6532
|
+
className: css$23.onboardingStage,
|
|
6061
6533
|
children
|
|
6062
6534
|
})]
|
|
6063
6535
|
}), document.body);
|
|
@@ -6078,28 +6550,28 @@ function RiskConfirmation({ open, title, description, acknowledgeLabel, cancelLa
|
|
|
6078
6550
|
onClose: onCancel,
|
|
6079
6551
|
title,
|
|
6080
6552
|
closeLabel,
|
|
6081
|
-
className: css$
|
|
6082
|
-
contentClassName: css$
|
|
6553
|
+
className: css$24.confirmation ?? "",
|
|
6554
|
+
contentClassName: css$24.confirmationContent ?? "",
|
|
6083
6555
|
footer: jsxs(Fragment, { children: [jsx(Button, {
|
|
6084
6556
|
variant: "outline",
|
|
6085
|
-
className: css$
|
|
6557
|
+
className: css$24.modalAction,
|
|
6086
6558
|
onClick: onCancel,
|
|
6087
6559
|
children: cancelLabel
|
|
6088
6560
|
}), jsx(Button, {
|
|
6089
6561
|
variant: "primary",
|
|
6090
|
-
className: css$
|
|
6562
|
+
className: css$24.confirmAction,
|
|
6091
6563
|
disabled: disabled || !acknowledged,
|
|
6092
6564
|
onClick: onConfirm,
|
|
6093
6565
|
children: confirmLabel
|
|
6094
6566
|
})] }),
|
|
6095
6567
|
children: [jsxs("div", {
|
|
6096
|
-
className: css$
|
|
6568
|
+
className: css$24.warning,
|
|
6097
6569
|
children: [jsx(IconWarningOutlineRegular, {
|
|
6098
6570
|
size: 18,
|
|
6099
|
-
className: css$
|
|
6571
|
+
className: css$24.warningIcon
|
|
6100
6572
|
}), jsx("p", { children: description })]
|
|
6101
6573
|
}), jsxs("label", {
|
|
6102
|
-
className: css$
|
|
6574
|
+
className: css$24.acknowledgement,
|
|
6103
6575
|
children: [jsx("input", {
|
|
6104
6576
|
type: "checkbox",
|
|
6105
6577
|
checked: acknowledged,
|
|
@@ -6147,47 +6619,47 @@ function ConnectionIndicator({ state, disconnectedLabel, connectingLabel, recove
|
|
|
6147
6619
|
};
|
|
6148
6620
|
}, [state, rendered]);
|
|
6149
6621
|
if (rendered === void 0) return null;
|
|
6150
|
-
const leavingClass = leaving ? ` ${css$
|
|
6622
|
+
const leavingClass = leaving ? ` ${css$25.leaving}` : "";
|
|
6151
6623
|
if (rendered === "recovered") return jsxs("div", {
|
|
6152
|
-
className: `${css$
|
|
6624
|
+
className: `${css$25.indicator} ${css$25.success}${leavingClass}`,
|
|
6153
6625
|
role: "status",
|
|
6154
6626
|
"aria-label": recoveredLabel,
|
|
6155
6627
|
children: [jsx("span", {
|
|
6156
|
-
className: css$
|
|
6628
|
+
className: css$25.icon,
|
|
6157
6629
|
"aria-hidden": "true",
|
|
6158
6630
|
children: jsx(IconCheckOutlineRegular, { size: 14 })
|
|
6159
6631
|
}), jsx("span", {
|
|
6160
|
-
className: css$
|
|
6632
|
+
className: css$25.label,
|
|
6161
6633
|
children: recoveredLabel
|
|
6162
6634
|
})]
|
|
6163
6635
|
});
|
|
6164
6636
|
const connecting = rendered === "connecting";
|
|
6165
6637
|
return jsxs("button", {
|
|
6166
6638
|
type: "button",
|
|
6167
|
-
className: `${css$
|
|
6639
|
+
className: `${css$25.indicator} ${css$25.warning}${leavingClass}`,
|
|
6168
6640
|
"data-phase": rendered,
|
|
6169
6641
|
"aria-label": connecting ? restartActionLabel : reconnectActionLabel,
|
|
6170
6642
|
onClick: onReconnect,
|
|
6171
6643
|
children: [jsx("span", {
|
|
6172
|
-
className: css$
|
|
6644
|
+
className: css$25.icon,
|
|
6173
6645
|
"aria-hidden": "true",
|
|
6174
6646
|
children: connecting ? jsx(IconLoadingOutlineRegular, {
|
|
6175
6647
|
size: 14,
|
|
6176
|
-
className: css$
|
|
6648
|
+
className: css$25.spinner
|
|
6177
6649
|
}) : jsx(IconRefreshOutlineRegular, { size: 14 })
|
|
6178
6650
|
}), jsx("span", {
|
|
6179
|
-
className: css$
|
|
6651
|
+
className: css$25.label,
|
|
6180
6652
|
children: connecting ? jsxs(Fragment, { children: [connectingLabel, jsxs("span", {
|
|
6181
|
-
className: css$
|
|
6653
|
+
className: css$25.dots,
|
|
6182
6654
|
"aria-hidden": "true",
|
|
6183
6655
|
children: [
|
|
6184
6656
|
jsx("span", { children: "." }),
|
|
6185
6657
|
jsx("span", {
|
|
6186
|
-
className: css$
|
|
6658
|
+
className: css$25.secondDot,
|
|
6187
6659
|
children: "."
|
|
6188
6660
|
}),
|
|
6189
6661
|
jsx("span", {
|
|
6190
|
-
className: css$
|
|
6662
|
+
className: css$25.thirdDot,
|
|
6191
6663
|
children: "."
|
|
6192
6664
|
})
|
|
6193
6665
|
]
|
|
@@ -7246,7 +7718,7 @@ function FileTypeIcon(props) {
|
|
|
7246
7718
|
type: resolvedType,
|
|
7247
7719
|
size,
|
|
7248
7720
|
className
|
|
7249
|
-
}) : glyph(resolvedType, size, clsx(css$
|
|
7721
|
+
}) : glyph(resolvedType, size, clsx(css$26.icon, css$26[resolvedType], className));
|
|
7250
7722
|
}
|
|
7251
7723
|
//#endregion
|
|
7252
7724
|
//#region lib/types/SiteGlyph.js
|
|
@@ -7544,22 +8016,244 @@ function PathLabel({ path, className, ...attributes }) {
|
|
|
7544
8016
|
return jsx("span", {
|
|
7545
8017
|
...attributes,
|
|
7546
8018
|
ref: boxRef,
|
|
7547
|
-
className: clsx(css$
|
|
8019
|
+
className: clsx(css$27.path, className),
|
|
7548
8020
|
title: path,
|
|
7549
8021
|
"data-path-label": true,
|
|
7550
8022
|
children: jsxs("span", {
|
|
7551
8023
|
ref: textRef,
|
|
7552
|
-
className: css$
|
|
8024
|
+
className: css$27.text,
|
|
7553
8025
|
children: [directory !== "" && jsx("span", {
|
|
7554
|
-
className: css$
|
|
8026
|
+
className: css$27.directory,
|
|
7555
8027
|
children: directory
|
|
7556
8028
|
}), jsx("span", {
|
|
7557
|
-
className: css$
|
|
8029
|
+
className: css$27.name,
|
|
7558
8030
|
children: name
|
|
7559
8031
|
})]
|
|
7560
8032
|
})
|
|
7561
8033
|
});
|
|
7562
8034
|
}
|
|
8035
|
+
//#endregion
|
|
8036
|
+
//#region lib/types/use-clipboard-copy.js
|
|
8037
|
+
/** Clipboard writes with component-owned success feedback and cleanup. */
|
|
8038
|
+
/**
|
|
8039
|
+
* Keep clipboard feedback local to the mounted control and release its reset timer on unmount.
|
|
8040
|
+
* @param text - Exact text to copy when the action is invoked.
|
|
8041
|
+
* @returns Success feedback and a callback stable while text and feedback are unchanged.
|
|
8042
|
+
*/
|
|
8043
|
+
function useClipboardCopy(text) {
|
|
8044
|
+
const [copied, setCopied] = useState(false);
|
|
8045
|
+
const copyPending = useRef(false);
|
|
8046
|
+
const copyTimer = useRef(null);
|
|
8047
|
+
const copyEpoch = useRef(0);
|
|
8048
|
+
useEffect(() => () => {
|
|
8049
|
+
copyEpoch.current += 1;
|
|
8050
|
+
copyPending.current = false;
|
|
8051
|
+
if (copyTimer.current !== null) clearTimeout(copyTimer.current);
|
|
8052
|
+
}, []);
|
|
8053
|
+
return {
|
|
8054
|
+
copied,
|
|
8055
|
+
onCopy: useCallback(() => {
|
|
8056
|
+
if (copied || copyPending.current) return;
|
|
8057
|
+
const epoch = copyEpoch.current;
|
|
8058
|
+
copyPending.current = true;
|
|
8059
|
+
writeClipboard(text).then((ok) => {
|
|
8060
|
+
if (epoch !== copyEpoch.current) return;
|
|
8061
|
+
copyPending.current = false;
|
|
8062
|
+
if (!ok) return;
|
|
8063
|
+
setCopied(true);
|
|
8064
|
+
copyTimer.current = window.setTimeout(() => {
|
|
8065
|
+
copyTimer.current = null;
|
|
8066
|
+
setCopied(false);
|
|
8067
|
+
}, 1e3);
|
|
8068
|
+
});
|
|
8069
|
+
}, [copied, text])
|
|
8070
|
+
};
|
|
8071
|
+
}
|
|
8072
|
+
//#endregion
|
|
8073
|
+
//#region lib/types/QuotePreview.js
|
|
8074
|
+
/**
|
|
8075
|
+
* Compact presentation of one quoted excerpt inside sent text: a quote mark,
|
|
8076
|
+
* the width-capped excerpt, and the copy and jump controls the surrounding
|
|
8077
|
+
* surface enables. A clipped excerpt opens its full text on hover or keyboard
|
|
8078
|
+
* focus, so the excerpt in the flow stays one line.
|
|
8079
|
+
*/
|
|
8080
|
+
function QuoteMarkIcon({ className }) {
|
|
8081
|
+
return jsx("svg", {
|
|
8082
|
+
width: 16,
|
|
8083
|
+
height: 16,
|
|
8084
|
+
className,
|
|
8085
|
+
viewBox: "0 0 16 16",
|
|
8086
|
+
fill: "none",
|
|
8087
|
+
"aria-hidden": true,
|
|
8088
|
+
children: jsxs("g", {
|
|
8089
|
+
transform: "translate(6.8 2.4)",
|
|
8090
|
+
children: [jsx("path", {
|
|
8091
|
+
d: "M2.8125 0.583008L3.2002 1.16602C2.43912 1.6713 1.88491 2.41238 1.60547 3.25684H2.98926V6.70605H0V4.52148C1.96593e-05 2.70313 0.9109 1.00575 2.42578 0L2.8125 0.583008Z",
|
|
8092
|
+
fill: "currentColor"
|
|
8093
|
+
}), jsx("path", {
|
|
8094
|
+
d: "M7.50781 0.583008L7.89551 1.16602C7.13443 1.6713 6.58022 2.41238 6.30078 3.25684H7.68457V6.70605H4.69531V4.52148C4.69533 2.70313 5.60621 1.00575 7.12109 0L7.50781 0.583008Z",
|
|
8095
|
+
fill: "currentColor"
|
|
8096
|
+
})]
|
|
8097
|
+
})
|
|
8098
|
+
});
|
|
8099
|
+
}
|
|
8100
|
+
/**
|
|
8101
|
+
* Render one quoted excerpt with its copy and optional jump controls.
|
|
8102
|
+
* @param props - excerpt text, control names, and the reveal action.
|
|
8103
|
+
* @returns the quote surface, with the full text on hover while clipped.
|
|
8104
|
+
*/
|
|
8105
|
+
function QuotePreview({ text, labels, onJump }) {
|
|
8106
|
+
const sourceRef = useRef(null);
|
|
8107
|
+
const [truncated, setTruncated] = useState(false);
|
|
8108
|
+
const { copied, onCopy } = useClipboardCopy(text);
|
|
8109
|
+
useLayoutEffect(() => {
|
|
8110
|
+
const source = sourceRef.current;
|
|
8111
|
+
/* v8 ignore next -- the ref is attached before layout effects run. */
|
|
8112
|
+
if (source === null) return;
|
|
8113
|
+
const measure = () => {
|
|
8114
|
+
setTruncated(source.scrollWidth > source.clientWidth);
|
|
8115
|
+
};
|
|
8116
|
+
measure();
|
|
8117
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
8118
|
+
const observer = new ResizeObserver(measure);
|
|
8119
|
+
observer.observe(source);
|
|
8120
|
+
return () => {
|
|
8121
|
+
observer.disconnect();
|
|
8122
|
+
};
|
|
8123
|
+
}, [text]);
|
|
8124
|
+
const preview = jsxs("span", {
|
|
8125
|
+
className: css$28.quote,
|
|
8126
|
+
"data-quote-preview": "",
|
|
8127
|
+
children: [
|
|
8128
|
+
jsx("span", {
|
|
8129
|
+
className: css$28.marker,
|
|
8130
|
+
"aria-hidden": true,
|
|
8131
|
+
children: jsx(QuoteMarkIcon, { className: css$28.quoteMark })
|
|
8132
|
+
}),
|
|
8133
|
+
jsx("span", {
|
|
8134
|
+
ref: sourceRef,
|
|
8135
|
+
className: css$28.source,
|
|
8136
|
+
"data-quote-preview-text": "",
|
|
8137
|
+
children: text
|
|
8138
|
+
}),
|
|
8139
|
+
jsx("button", {
|
|
8140
|
+
type: "button",
|
|
8141
|
+
className: css$28.action,
|
|
8142
|
+
"aria-label": copied ? labels.copied : labels.copy,
|
|
8143
|
+
"data-quote-copy": "",
|
|
8144
|
+
onPointerDown: (event) => {
|
|
8145
|
+
event.preventDefault();
|
|
8146
|
+
event.stopPropagation();
|
|
8147
|
+
},
|
|
8148
|
+
onClick: (event) => {
|
|
8149
|
+
event.preventDefault();
|
|
8150
|
+
event.stopPropagation();
|
|
8151
|
+
onCopy();
|
|
8152
|
+
},
|
|
8153
|
+
children: copied ? jsx(IconCheckOutlineRegular, { size: 12 }) : jsx(IconCopyOutlineRegular, { size: 12 })
|
|
8154
|
+
}),
|
|
8155
|
+
onJump !== void 0 && jsx("button", {
|
|
8156
|
+
type: "button",
|
|
8157
|
+
className: css$28.action,
|
|
8158
|
+
"aria-label": labels.jump,
|
|
8159
|
+
"data-quote-jump": "",
|
|
8160
|
+
onClick: onJump,
|
|
8161
|
+
children: jsx(IconRightUpOutlineRegular, { size: 12 })
|
|
8162
|
+
})
|
|
8163
|
+
]
|
|
8164
|
+
});
|
|
8165
|
+
return jsx(HoverCard, {
|
|
8166
|
+
inline: true,
|
|
8167
|
+
openDelayMs: 0,
|
|
8168
|
+
disabled: !truncated,
|
|
8169
|
+
anchor: preview,
|
|
8170
|
+
content: jsx("span", {
|
|
8171
|
+
className: css$28.popoverText,
|
|
8172
|
+
"data-quote-popover": "",
|
|
8173
|
+
"aria-hidden": "true",
|
|
8174
|
+
children: text
|
|
8175
|
+
})
|
|
8176
|
+
});
|
|
8177
|
+
}
|
|
8178
|
+
//#endregion
|
|
8179
|
+
//#region lib/types/quote-text.js
|
|
8180
|
+
/**
|
|
8181
|
+
* Explicit Markdown blockquote recognition and its source token, shared by the
|
|
8182
|
+
* user-text projection and any composer-side quote gesture.
|
|
8183
|
+
*
|
|
8184
|
+
* A quote is ordinary message text: consecutive lines that start with up to
|
|
8185
|
+
* three spaces and one `>` marker, plus one optional trailing space or tab.
|
|
8186
|
+
* Inline greater-than signs stay ordinary text. The first decoded line may name
|
|
8187
|
+
* the message the quote came from as `[quoted](qilin-quote:<node-key>)`, which
|
|
8188
|
+
* the projection consumes for a jump control and never shows as excerpt text.
|
|
8189
|
+
* Line endings normalize to `\n` on decode, so a message sent from any editor
|
|
8190
|
+
* prints the same excerpt.
|
|
8191
|
+
*
|
|
8192
|
+
* This module is the single home for both directions of that form: the
|
|
8193
|
+
* projection's scanner and decoder, and the serializer a quote gesture hands
|
|
8194
|
+
* the composer. Adding the composer's insertion seat needs no second copy.
|
|
8195
|
+
*/
|
|
8196
|
+
/** Scheme of the optional first line naming the quoted message's Chat node key. */
|
|
8197
|
+
const QUOTE_WIRE_SCHEME = "qilin-quote:";
|
|
8198
|
+
/** Marker line a quote block may open with: `[label](qilin-quote:<key>)`. */
|
|
8199
|
+
const SOURCE_LINE_RE = /* @__PURE__ */ new RegExp("^\\[[^\\]\\n]*\\]\\(qilin-quote:([^)\\s]+)\\)$", "u");
|
|
8200
|
+
/**
|
|
8201
|
+
* Remove one blockquote marker from each line and normalize line endings.
|
|
8202
|
+
* @param text - consecutive blockquote lines, with up to three leading spaces.
|
|
8203
|
+
* @returns excerpt text with original line endings normalized.
|
|
8204
|
+
*/
|
|
8205
|
+
function quoteTextOf(text) {
|
|
8206
|
+
return text.replace(/\r\n?/gu, "\n").replace(/^ {0,3}>[ \t]?/gmu, "");
|
|
8207
|
+
}
|
|
8208
|
+
/**
|
|
8209
|
+
* Recognize consecutive explicitly marked Markdown blockquote lines.
|
|
8210
|
+
* @param text - complete original message text.
|
|
8211
|
+
* @returns ordered nonblank quote spans; inline greater-than signs remain ordinary text.
|
|
8212
|
+
*/
|
|
8213
|
+
function messageQuoteRanges(text) {
|
|
8214
|
+
const ranges = [];
|
|
8215
|
+
for (const match of text.matchAll(/^ {0,3}>[^\r\n]*(?:\r?\n {0,3}>[^\r\n]*)*/gmu)) if (quoteTextOf(match[0]).trim() !== "") ranges.push({
|
|
8216
|
+
start: match.index,
|
|
8217
|
+
end: match.index + match[0].length
|
|
8218
|
+
});
|
|
8219
|
+
return ranges;
|
|
8220
|
+
}
|
|
8221
|
+
/**
|
|
8222
|
+
* Split one decoded quote block into the source it names and its excerpt.
|
|
8223
|
+
* @param decoded - block text already stripped of its quote markers.
|
|
8224
|
+
* @returns the excerpt shown to the reader and the named source, if present.
|
|
8225
|
+
*/
|
|
8226
|
+
function quotedExcerptOf(decoded) {
|
|
8227
|
+
const breakAt = decoded.indexOf("\n");
|
|
8228
|
+
const first = breakAt === -1 ? decoded : decoded.slice(0, breakAt);
|
|
8229
|
+
const match = SOURCE_LINE_RE.exec(first);
|
|
8230
|
+
if (match === null) return {
|
|
8231
|
+
text: decoded,
|
|
8232
|
+
source: void 0
|
|
8233
|
+
};
|
|
8234
|
+
return {
|
|
8235
|
+
text: breakAt === -1 ? "" : decoded.slice(breakAt + 1),
|
|
8236
|
+
source: match[1]
|
|
8237
|
+
};
|
|
8238
|
+
}
|
|
8239
|
+
/**
|
|
8240
|
+
* Serialize one excerpt as the blockquote text a quote gesture inserts.
|
|
8241
|
+
* @param text - excerpt to quote, as selected.
|
|
8242
|
+
* @param source - Chat node key of the quoted message, when the caller knows it.
|
|
8243
|
+
* @returns blockquote lines, one per excerpt line, with the source marker first.
|
|
8244
|
+
*/
|
|
8245
|
+
function quoteBlockTextOf(text, source) {
|
|
8246
|
+
const lines = text.replace(/\r\n?/gu, "\n").split("\n").map((line) => `> ${line}`);
|
|
8247
|
+
return source === void 0 ? lines.join("\n") : `> [quoted](${QUOTE_WIRE_SCHEME}${source})\n${lines.join("\n")}`;
|
|
8248
|
+
}
|
|
8249
|
+
/**
|
|
8250
|
+
* Serialize one selected excerpt into the message text a quote gesture inserts.
|
|
8251
|
+
* @param fragment - excerpt text and the source it names, if any.
|
|
8252
|
+
* @returns blockquote lines the composer receives as ordinary draft text.
|
|
8253
|
+
*/
|
|
8254
|
+
function quoteInsertionOf(fragment) {
|
|
8255
|
+
return quoteBlockTextOf(fragment.text, fragment.source);
|
|
8256
|
+
}
|
|
7563
8257
|
const prefixed = (literal, check, run, min, max = 0) => ({
|
|
7564
8258
|
literal,
|
|
7565
8259
|
kind: 2,
|
|
@@ -7756,7 +8450,7 @@ const MASK_RUNS = /* @__PURE__ */ new RegExp("(\\*+)", "u");
|
|
|
7756
8450
|
*/
|
|
7757
8451
|
function EyeGlyph({ path }) {
|
|
7758
8452
|
return jsx("svg", {
|
|
7759
|
-
className: css$
|
|
8453
|
+
className: css$29.icon,
|
|
7760
8454
|
viewBox: "0 0 16 16",
|
|
7761
8455
|
fill: "none",
|
|
7762
8456
|
stroke: "currentColor",
|
|
@@ -7780,7 +8474,7 @@ function MaskedText({ masked }) {
|
|
|
7780
8474
|
offset += part.length;
|
|
7781
8475
|
if (part === "") return null;
|
|
7782
8476
|
return part.startsWith("*") ? jsx("span", {
|
|
7783
|
-
className: css$
|
|
8477
|
+
className: css$29.mask,
|
|
7784
8478
|
children: part
|
|
7785
8479
|
}, key) : jsx("span", { children: part }, key);
|
|
7786
8480
|
});
|
|
@@ -7796,11 +8490,11 @@ function SecretText({ masked, raw, labels }) {
|
|
|
7796
8490
|
const [revealedRaw, setRevealedRaw] = useState();
|
|
7797
8491
|
const revealed = labels !== void 0 && revealedRaw === raw;
|
|
7798
8492
|
return jsxs("span", {
|
|
7799
|
-
className: css$
|
|
8493
|
+
className: css$29.secret,
|
|
7800
8494
|
"data-secret": "",
|
|
7801
8495
|
children: [jsx("span", { children: revealed ? raw : jsx(MaskedText, { masked }) }), labels !== void 0 && jsx("button", {
|
|
7802
8496
|
type: "button",
|
|
7803
|
-
className: css$
|
|
8497
|
+
className: css$29.toggle,
|
|
7804
8498
|
"aria-pressed": revealed,
|
|
7805
8499
|
"aria-label": revealed ? labels.hide : labels.reveal,
|
|
7806
8500
|
onClick: () => {
|
|
@@ -8326,9 +9020,10 @@ const TRAILING_PUNCTUATION_RE = /[.,;:!?,。;:!?]+$/u;
|
|
|
8326
9020
|
* @param slashKind - the chip kind those tokens render as.
|
|
8327
9021
|
* @param references - optional file and skill preview actions; session and command tokens stay labels.
|
|
8328
9022
|
* @param secretLabels - reveal-control copy for credentials; absent in text that stays masked.
|
|
9023
|
+
* @param quotes - quote presentation for blockquoted spans; absent renders those characters as plain text.
|
|
8329
9024
|
* @returns inline nodes covering the whole text.
|
|
8330
9025
|
*/
|
|
8331
|
-
function projectUserText(text, sessionLabels, slashNames = [], slashKind = "skill", references, secretLabels) {
|
|
9026
|
+
function projectUserText(text, sessionLabels, slashNames = [], slashKind = "skill", references, secretLabels, quotes) {
|
|
8332
9027
|
const secrets = scanSecrets(text);
|
|
8333
9028
|
const ranges = secrets.map((secret) => ({
|
|
8334
9029
|
start: secret.start,
|
|
@@ -8337,6 +9032,17 @@ function projectUserText(text, sessionLabels, slashNames = [], slashKind = "skil
|
|
|
8337
9032
|
masked: secret.masked
|
|
8338
9033
|
}));
|
|
8339
9034
|
const maskedOver = (start, end) => secrets.some((secret) => start < secret.end && secret.start < end);
|
|
9035
|
+
if (quotes !== void 0) for (const range of messageQuoteRanges(text)) {
|
|
9036
|
+
if (maskedOver(range.start, range.end)) continue;
|
|
9037
|
+
ranges.push({
|
|
9038
|
+
start: range.start,
|
|
9039
|
+
end: range.end,
|
|
9040
|
+
kind: "quote",
|
|
9041
|
+
excerpt: quotedExcerptOf(quoteTextOf(text.slice(range.start, range.end))),
|
|
9042
|
+
labels: quotes.labels,
|
|
9043
|
+
onJump: quotes.onJump
|
|
9044
|
+
});
|
|
9045
|
+
}
|
|
8340
9046
|
SESSION_WIRE_RE.lastIndex = 0;
|
|
8341
9047
|
let wire;
|
|
8342
9048
|
while ((wire = SESSION_WIRE_RE.exec(text)) !== null) {
|
|
@@ -8384,7 +9090,7 @@ function projectUserText(text, sessionLabels, slashNames = [], slashKind = "skil
|
|
|
8384
9090
|
let cursor = 0;
|
|
8385
9091
|
const pushPlain = (from, to) => {
|
|
8386
9092
|
parts.push(jsx("span", {
|
|
8387
|
-
className: css$
|
|
9093
|
+
className: css$30.plainRun,
|
|
8388
9094
|
children: text.slice(from, to)
|
|
8389
9095
|
}, `t${from}`));
|
|
8390
9096
|
};
|
|
@@ -8401,27 +9107,46 @@ function projectUserText(text, sessionLabels, slashNames = [], slashKind = "skil
|
|
|
8401
9107
|
cursor = end;
|
|
8402
9108
|
continue;
|
|
8403
9109
|
}
|
|
9110
|
+
if (range.kind === "quote") {
|
|
9111
|
+
const { excerpt, labels, onJump } = range;
|
|
9112
|
+
const { source } = excerpt;
|
|
9113
|
+
parts.push(jsx(QuotePreview, {
|
|
9114
|
+
text: excerpt.text,
|
|
9115
|
+
labels,
|
|
9116
|
+
...source === void 0 || onJump === void 0 ? {} : { onJump: () => {
|
|
9117
|
+
onJump(source);
|
|
9118
|
+
} }
|
|
9119
|
+
}, `q${tokenStart}`));
|
|
9120
|
+
cursor = end;
|
|
9121
|
+
continue;
|
|
9122
|
+
}
|
|
8404
9123
|
const { label, kind } = range;
|
|
8405
9124
|
const referenceKind = kind === "session" ? "session" : label.startsWith("@") ? label.replace(/^@"|"$/gu, "").endsWith("/") ? "folder" : "file" : void 0;
|
|
8406
9125
|
const displayLabel = range.display ?? (referenceKind === void 0 ? label : referenceKind === "session" ? label.slice(1) : label.slice(1).replace(/^"|"$/gu, "").split(/[\\/]/u).filter(Boolean).at(-1) ?? label.slice(1));
|
|
8407
9126
|
const contents = jsxs(Fragment, { children: [referenceKind !== void 0 && jsx(ReferenceIconRegular, {
|
|
8408
9127
|
kind: referenceKind,
|
|
8409
9128
|
size: 16,
|
|
8410
|
-
className: css$
|
|
9129
|
+
className: css$30.refIcon
|
|
8411
9130
|
}), displayLabel] });
|
|
8412
9131
|
const open = references === void 0 ? void 0 : referenceKind === "file" ? () => {
|
|
8413
9132
|
references.openFile(label.slice(1).replace(/^"|"$/gu, ""));
|
|
8414
9133
|
} : referenceKind === void 0 && slashKind === "skill" ? () => {
|
|
8415
9134
|
references.openSkill(label.slice(1));
|
|
8416
9135
|
} : void 0;
|
|
8417
|
-
const className = clsx(css$
|
|
9136
|
+
const className = clsx(css$30.refChip, referenceKind === void 0 && css$30.slashChip);
|
|
9137
|
+
const menuProps = references?.contextMenuProps?.({
|
|
9138
|
+
kind: referenceKind ?? slashKind,
|
|
9139
|
+
label
|
|
9140
|
+
});
|
|
8418
9141
|
parts.push(open === void 0 ? jsx("span", {
|
|
9142
|
+
...menuProps,
|
|
8419
9143
|
className,
|
|
8420
9144
|
"data-ref-chip": referenceKind ?? slashKind,
|
|
8421
9145
|
title: label,
|
|
8422
9146
|
children: contents
|
|
8423
9147
|
}, tokenStart) : jsx("button", {
|
|
8424
9148
|
type: "button",
|
|
9149
|
+
...menuProps,
|
|
8425
9150
|
className: clsx(className, markdownCss.fileMention),
|
|
8426
9151
|
"data-ref-chip": referenceKind ?? slashKind,
|
|
8427
9152
|
title: label,
|
|
@@ -8434,7 +9159,7 @@ function projectUserText(text, sessionLabels, slashNames = [], slashKind = "skil
|
|
|
8434
9159
|
cursor = end;
|
|
8435
9160
|
}
|
|
8436
9161
|
if (parts.length === 0) return jsx("span", {
|
|
8437
|
-
className: css$
|
|
9162
|
+
className: css$30.plainRun,
|
|
8438
9163
|
children: text
|
|
8439
9164
|
});
|
|
8440
9165
|
if (cursor < text.length) pushPlain(cursor, text.length);
|
|
@@ -8454,6 +9179,16 @@ const FADE_MS = 1e3;
|
|
|
8454
9179
|
* transformed or filtered ancestor cannot trap the fixed banner in that
|
|
8455
9180
|
* ancestor's box.
|
|
8456
9181
|
*
|
|
9182
|
+
* Actions extend the sentence with inline choices: each renders its plain-text
|
|
9183
|
+
* `prefix` (a connective such as "or") followed by its localized `label` as
|
|
9184
|
+
* clickable text inside the banner. Each press is the owner's to handle (undo
|
|
9185
|
+
* the reported change, then unmount the toast). The banner surface stays
|
|
9186
|
+
* click-through — only the action text takes the pointer — and a fully faded
|
|
9187
|
+
* banner takes no input at all.
|
|
9188
|
+
*
|
|
9189
|
+
* With an unchanged holdMs, a parent rerender does not extend the lifetime:
|
|
9190
|
+
* the completion call reaches the latest onDone handler.
|
|
9191
|
+
*
|
|
8457
9192
|
* The hold is the owner's to set, because how long a banner has to stay
|
|
8458
9193
|
* depends on how much there is to read: a one-line limit lands in the default
|
|
8459
9194
|
* window, while a failure that names what broke does not. One value drives
|
|
@@ -8465,6 +9200,9 @@ const FADE_MS = 1e3;
|
|
|
8465
9200
|
* under `tone="success"`, which brings its own glyph.
|
|
8466
9201
|
* @param props.tone - 'success' renders the success check as the leading
|
|
8467
9202
|
* glyph; omitted, the icon seat keeps its warning tint.
|
|
9203
|
+
* @param props.actions - optional inline actions continuing the sentence;
|
|
9204
|
+
* each renders its plain-text `prefix` followed by its localized `label`
|
|
9205
|
+
* as clickable text flowing after `text` as one sentence.
|
|
8468
9206
|
* @param props.holdMs - full-opacity hold before the fade; defaults to 3000.
|
|
8469
9207
|
* @param props.anchor - optional element whose horizontal center the banner
|
|
8470
9208
|
* follows (e.g. the composer card, so the banner centers over the chat column
|
|
@@ -8472,13 +9210,19 @@ const FADE_MS = 1e3;
|
|
|
8472
9210
|
* @param props.onDone - called once the fade completes; unmount the toast here.
|
|
8473
9211
|
* @returns the floating banner.
|
|
8474
9212
|
*/
|
|
8475
|
-
function Toast({ text, icon, tone, anchor, holdMs = HOLD_MS, onDone }) {
|
|
9213
|
+
function Toast({ text, icon, tone, anchor, holdMs = HOLD_MS, actions, onDone }) {
|
|
9214
|
+
const latestOnDone = useRef(onDone);
|
|
9215
|
+
useLayoutEffect(() => {
|
|
9216
|
+
latestOnDone.current = onDone;
|
|
9217
|
+
}, [onDone]);
|
|
8476
9218
|
useEffect(() => {
|
|
8477
|
-
const timer = setTimeout(
|
|
9219
|
+
const timer = setTimeout(() => {
|
|
9220
|
+
latestOnDone.current();
|
|
9221
|
+
}, holdMs + FADE_MS);
|
|
8478
9222
|
return () => {
|
|
8479
9223
|
clearTimeout(timer);
|
|
8480
9224
|
};
|
|
8481
|
-
}, [holdMs
|
|
9225
|
+
}, [holdMs]);
|
|
8482
9226
|
const [left, setLeft] = useState(null);
|
|
8483
9227
|
useLayoutEffect(() => {
|
|
8484
9228
|
if (anchor == null) return;
|
|
@@ -8493,23 +9237,28 @@ function Toast({ text, icon, tone, anchor, holdMs = HOLD_MS, onDone }) {
|
|
|
8493
9237
|
};
|
|
8494
9238
|
}, [anchor]);
|
|
8495
9239
|
return createPortal(jsxs("div", {
|
|
8496
|
-
className: css$
|
|
9240
|
+
className: css$31.toast,
|
|
8497
9241
|
role: "alert",
|
|
8498
9242
|
style: {
|
|
8499
9243
|
...left === null ? {} : { left },
|
|
8500
9244
|
"--qilin-toast-hold": `${String(holdMs)}ms`
|
|
8501
9245
|
},
|
|
8502
9246
|
children: [tone === "success" ? jsx("span", {
|
|
8503
|
-
className: `${css$
|
|
9247
|
+
className: `${css$31.icon} ${css$31.success}`,
|
|
8504
9248
|
"aria-hidden": true,
|
|
8505
9249
|
children: jsx(IconCheckOutlineRegular, {})
|
|
8506
9250
|
}) : icon !== void 0 && jsx("span", {
|
|
8507
|
-
className: css$
|
|
9251
|
+
className: css$31.icon,
|
|
8508
9252
|
"aria-hidden": true,
|
|
8509
9253
|
children: icon
|
|
8510
|
-
}),
|
|
8511
|
-
className: css$
|
|
8512
|
-
children: text
|
|
9254
|
+
}), jsxs("span", {
|
|
9255
|
+
className: css$31.text,
|
|
9256
|
+
children: [text, actions?.map((action) => jsxs(Fragment$1, { children: [action.prefix, jsx("button", {
|
|
9257
|
+
type: "button",
|
|
9258
|
+
className: css$31.action,
|
|
9259
|
+
onClick: action.onClick,
|
|
9260
|
+
children: action.label
|
|
9261
|
+
})] }, action.label))]
|
|
8513
9262
|
})]
|
|
8514
9263
|
}), document.body);
|
|
8515
9264
|
}
|
|
@@ -8544,29 +9293,17 @@ function fileSizeRoundedText(bytes) {
|
|
|
8544
9293
|
return `${bytes} B`;
|
|
8545
9294
|
}
|
|
8546
9295
|
//#endregion
|
|
8547
|
-
//#region lib/types/
|
|
8548
|
-
/** How long the `copied` flag stays true after a successful write, in ms. */
|
|
8549
|
-
const COPIED_FEEDBACK_MS = 1e3;
|
|
9296
|
+
//#region lib/types/UserMessageBubble.js
|
|
8550
9297
|
/**
|
|
8551
|
-
*
|
|
8552
|
-
* @param
|
|
8553
|
-
* @returns
|
|
9298
|
+
* Render user-style message content without row placement or actions.
|
|
9299
|
+
* @param props - Native div attributes and the caller-owned content.
|
|
9300
|
+
* @returns A theme-aware message surface that wraps its content.
|
|
8554
9301
|
*/
|
|
8555
|
-
function
|
|
8556
|
-
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
if (copied) return;
|
|
8561
|
-
writeClipboard(text).then((ok) => {
|
|
8562
|
-
if (!ok) return;
|
|
8563
|
-
setCopied(true);
|
|
8564
|
-
window.setTimeout(() => {
|
|
8565
|
-
setCopied(false);
|
|
8566
|
-
}, COPIED_FEEDBACK_MS);
|
|
8567
|
-
});
|
|
8568
|
-
}, [copied, text])
|
|
8569
|
-
};
|
|
9302
|
+
function UserMessageBubble({ className, ...props }) {
|
|
9303
|
+
return jsx("div", {
|
|
9304
|
+
...props,
|
|
9305
|
+
className: clsx(css$32.bubble, className)
|
|
9306
|
+
});
|
|
8570
9307
|
}
|
|
8571
9308
|
//#endregion
|
|
8572
9309
|
//#region lib/types/relative-time.js
|
|
@@ -8802,7 +9539,7 @@ function JsonCopyAction({ store, target, persistent, labels, onCopy, onClose })
|
|
|
8802
9539
|
const object = typeof target.value === "object" && target.value !== null;
|
|
8803
9540
|
const copyTitle = state === "copied" ? labels.copied : state === "failed" ? labels.copyFailed : object ? labels.copyPrettyJson : labels.copyValue;
|
|
8804
9541
|
return jsx("span", {
|
|
8805
|
-
className: css$
|
|
9542
|
+
className: css$33.copySlot,
|
|
8806
9543
|
children: (persistent || snapshot !== void 0) && jsx(Menu, {
|
|
8807
9544
|
open: snapshot?.menuOpen === true,
|
|
8808
9545
|
compact: true,
|
|
@@ -8811,7 +9548,7 @@ function JsonCopyAction({ store, target, persistent, labels, onCopy, onClose })
|
|
|
8811
9548
|
anchor: jsx("button", {
|
|
8812
9549
|
ref: buttonRef,
|
|
8813
9550
|
type: "button",
|
|
8814
|
-
className: css$
|
|
9551
|
+
className: css$33.actionButton,
|
|
8815
9552
|
"data-json-copy-button": true,
|
|
8816
9553
|
"data-state": state,
|
|
8817
9554
|
"aria-label": copyTitle,
|
|
@@ -8850,35 +9587,35 @@ function bracketOf(value) {
|
|
|
8850
9587
|
}
|
|
8851
9588
|
function previewPrimitive(value) {
|
|
8852
9589
|
if (value === null) return jsx("span", {
|
|
8853
|
-
className: css$
|
|
9590
|
+
className: css$33.keywordValue,
|
|
8854
9591
|
children: "null"
|
|
8855
9592
|
});
|
|
8856
9593
|
if (typeof value === "string") return jsx("span", {
|
|
8857
|
-
className: css$
|
|
9594
|
+
className: css$33.stringValue,
|
|
8858
9595
|
children: JSON.stringify(value)
|
|
8859
9596
|
});
|
|
8860
9597
|
if (typeof value === "number") return jsx("span", {
|
|
8861
|
-
className: css$
|
|
9598
|
+
className: css$33.numberValue,
|
|
8862
9599
|
children: String(value)
|
|
8863
9600
|
});
|
|
8864
9601
|
if (typeof value === "boolean") return jsx("span", {
|
|
8865
|
-
className: css$
|
|
9602
|
+
className: css$33.keywordValue,
|
|
8866
9603
|
children: String(value)
|
|
8867
9604
|
});
|
|
8868
9605
|
if (typeof value === "bigint") return jsx("span", {
|
|
8869
|
-
className: css$
|
|
9606
|
+
className: css$33.otherValue,
|
|
8870
9607
|
children: value.toString()
|
|
8871
9608
|
});
|
|
8872
9609
|
if (typeof value === "undefined") return jsx("span", {
|
|
8873
|
-
className: css$
|
|
9610
|
+
className: css$33.otherValue,
|
|
8874
9611
|
children: "undefined"
|
|
8875
9612
|
});
|
|
8876
9613
|
if (typeof value === "symbol") return jsx("span", {
|
|
8877
|
-
className: css$
|
|
9614
|
+
className: css$33.otherValue,
|
|
8878
9615
|
children: value.description ?? "Symbol"
|
|
8879
9616
|
});
|
|
8880
9617
|
if (typeof value === "function") return jsx("span", {
|
|
8881
|
-
className: css$
|
|
9618
|
+
className: css$33.otherValue,
|
|
8882
9619
|
children: value.name || "Function"
|
|
8883
9620
|
});
|
|
8884
9621
|
return null;
|
|
@@ -8892,71 +9629,71 @@ function previewValue(value, depth) {
|
|
|
8892
9629
|
const [open, close] = bracketOf(value);
|
|
8893
9630
|
return jsxs(Fragment, { children: [
|
|
8894
9631
|
jsx("span", {
|
|
8895
|
-
className: css$
|
|
9632
|
+
className: css$33.punctuation,
|
|
8896
9633
|
children: open
|
|
8897
9634
|
}),
|
|
8898
9635
|
depth >= PREVIEW_DEPTH_LIMIT ? jsx("span", {
|
|
8899
|
-
className: css$
|
|
9636
|
+
className: css$33.previewEllipsis,
|
|
8900
9637
|
children: "…"
|
|
8901
9638
|
}) : visible.map(([key, item], index) => jsxs("span", { children: [
|
|
8902
9639
|
index > 0 && jsx("span", {
|
|
8903
|
-
className: css$
|
|
9640
|
+
className: css$33.punctuation,
|
|
8904
9641
|
children: ", "
|
|
8905
9642
|
}),
|
|
8906
9643
|
!array && jsxs(Fragment, { children: [jsx("span", {
|
|
8907
|
-
className: css$
|
|
9644
|
+
className: css$33.previewProperty,
|
|
8908
9645
|
children: key
|
|
8909
9646
|
}), jsx("span", {
|
|
8910
|
-
className: css$
|
|
9647
|
+
className: css$33.punctuation,
|
|
8911
9648
|
children: ": "
|
|
8912
9649
|
})] }),
|
|
8913
9650
|
previewValue(item, depth + 1)
|
|
8914
9651
|
] }, key)),
|
|
8915
9652
|
depth < PREVIEW_DEPTH_LIMIT && entries.length > limit && jsx("span", {
|
|
8916
|
-
className: css$
|
|
9653
|
+
className: css$33.previewEllipsis,
|
|
8917
9654
|
children: ", …"
|
|
8918
9655
|
}),
|
|
8919
9656
|
jsx("span", {
|
|
8920
|
-
className: css$
|
|
9657
|
+
className: css$33.punctuation,
|
|
8921
9658
|
children: close
|
|
8922
9659
|
})
|
|
8923
9660
|
] });
|
|
8924
9661
|
}
|
|
8925
9662
|
function primitiveValue(value) {
|
|
8926
9663
|
if (value === null) return jsx("span", {
|
|
8927
|
-
className: css$
|
|
9664
|
+
className: css$33.keywordValue,
|
|
8928
9665
|
children: "null"
|
|
8929
9666
|
});
|
|
8930
9667
|
if (typeof value === "string") return jsx("span", {
|
|
8931
|
-
className: css$
|
|
9668
|
+
className: css$33.stringValue,
|
|
8932
9669
|
children: JSON.stringify(value)
|
|
8933
9670
|
});
|
|
8934
9671
|
if (typeof value === "boolean") return jsx("span", {
|
|
8935
|
-
className: css$
|
|
9672
|
+
className: css$33.keywordValue,
|
|
8936
9673
|
children: String(value)
|
|
8937
9674
|
});
|
|
8938
9675
|
if (typeof value === "number") return jsx("span", {
|
|
8939
|
-
className: css$
|
|
9676
|
+
className: css$33.numberValue,
|
|
8940
9677
|
children: String(value)
|
|
8941
9678
|
});
|
|
8942
9679
|
if (typeof value === "bigint") return jsx("span", {
|
|
8943
|
-
className: css$
|
|
9680
|
+
className: css$33.numberValue,
|
|
8944
9681
|
children: `${value.toString()}n`
|
|
8945
9682
|
});
|
|
8946
9683
|
if (value instanceof Date) return jsx("span", {
|
|
8947
|
-
className: css$
|
|
9684
|
+
className: css$33.otherValue,
|
|
8948
9685
|
children: value.toISOString()
|
|
8949
9686
|
});
|
|
8950
9687
|
if (typeof value === "function") return jsxs("span", {
|
|
8951
|
-
className: css$
|
|
9688
|
+
className: css$33.otherValue,
|
|
8952
9689
|
children: ["function() ", "{ }"]
|
|
8953
9690
|
});
|
|
8954
9691
|
if (typeof value === "undefined") return jsx("span", {
|
|
8955
|
-
className: css$
|
|
9692
|
+
className: css$33.otherValue,
|
|
8956
9693
|
children: "undefined"
|
|
8957
9694
|
});
|
|
8958
9695
|
return jsx("span", {
|
|
8959
|
-
className: css$
|
|
9696
|
+
className: css$33.otherValue,
|
|
8960
9697
|
children: value.toString()
|
|
8961
9698
|
});
|
|
8962
9699
|
}
|
|
@@ -8984,7 +9721,7 @@ function moveFocus(button, direction) {
|
|
|
8984
9721
|
function NodeField({ field, expandable, onToggle }) {
|
|
8985
9722
|
if (field === void 0) return null;
|
|
8986
9723
|
return jsxs("span", {
|
|
8987
|
-
className: clsx(css$
|
|
9724
|
+
className: clsx(css$33.label, expandable && css$33.clickableLabel),
|
|
8988
9725
|
onClick: expandable ? onToggle : void 0,
|
|
8989
9726
|
children: [fieldText(field), ":"]
|
|
8990
9727
|
});
|
|
@@ -9021,7 +9758,7 @@ function JsonString({ collapsedStringLines, stringWrapping, field, labels, lastE
|
|
|
9021
9758
|
if (!expanded) return;
|
|
9022
9759
|
const raw = rawRef.current;
|
|
9023
9760
|
const clips = [];
|
|
9024
|
-
const tree = raw.closest(`.${css$
|
|
9761
|
+
const tree = raw.closest(`.${css$33.root}`);
|
|
9025
9762
|
for (let parent = tree.parentElement; parent !== null; parent = parent.parentElement) if (/auto|scroll|hidden|clip/.test(getComputedStyle(parent).overflowY)) clips.push(parent);
|
|
9026
9763
|
const measure = () => {
|
|
9027
9764
|
let top = 0;
|
|
@@ -9050,33 +9787,33 @@ function JsonString({ collapsedStringLines, stringWrapping, field, labels, lastE
|
|
|
9050
9787
|
if (expanded) {
|
|
9051
9788
|
const fieldId = `${contentsId}-field`;
|
|
9052
9789
|
return jsxs("div", {
|
|
9053
|
-
className: css$
|
|
9790
|
+
className: css$33.stringField,
|
|
9054
9791
|
"data-expanded": true,
|
|
9055
9792
|
children: [
|
|
9056
9793
|
field !== void 0 && jsxs("span", {
|
|
9057
9794
|
id: fieldId,
|
|
9058
|
-
className: css$
|
|
9795
|
+
className: css$33.label,
|
|
9059
9796
|
children: [fieldText(field), ":"]
|
|
9060
9797
|
}),
|
|
9061
9798
|
jsx("pre", {
|
|
9062
9799
|
ref: rawRef,
|
|
9063
9800
|
id: contentsId,
|
|
9064
|
-
className: css$
|
|
9801
|
+
className: css$33.stringRaw,
|
|
9065
9802
|
"data-wrap": wrapped,
|
|
9066
9803
|
tabIndex: 0,
|
|
9067
9804
|
"aria-labelledby": field === void 0 ? void 0 : fieldId,
|
|
9068
9805
|
children: value
|
|
9069
9806
|
}),
|
|
9070
9807
|
!lastElement && jsx("span", {
|
|
9071
|
-
className: css$
|
|
9808
|
+
className: css$33.punctuation,
|
|
9072
9809
|
children: ","
|
|
9073
9810
|
}),
|
|
9074
9811
|
jsxs("div", {
|
|
9075
|
-
className: css$
|
|
9812
|
+
className: css$33.stringActions,
|
|
9076
9813
|
children: [
|
|
9077
9814
|
stringWrapping !== void 0 && jsx("button", {
|
|
9078
9815
|
type: "button",
|
|
9079
|
-
className: css$
|
|
9816
|
+
className: css$33.actionButton,
|
|
9080
9817
|
"aria-label": stringWrapping.label,
|
|
9081
9818
|
title: stringWrapping.label,
|
|
9082
9819
|
"aria-pressed": wrapped,
|
|
@@ -9090,7 +9827,7 @@ function JsonString({ collapsedStringLines, stringWrapping, field, labels, lastE
|
|
|
9090
9827
|
}),
|
|
9091
9828
|
jsx("button", {
|
|
9092
9829
|
type: "button",
|
|
9093
|
-
className: css$
|
|
9830
|
+
className: css$33.actionButton,
|
|
9094
9831
|
"aria-label": labels.collapseNode,
|
|
9095
9832
|
title: labels.collapseNode,
|
|
9096
9833
|
"aria-expanded": true,
|
|
@@ -9116,18 +9853,18 @@ function JsonString({ collapsedStringLines, stringWrapping, field, labels, lastE
|
|
|
9116
9853
|
});
|
|
9117
9854
|
}
|
|
9118
9855
|
return jsxs(Fragment, { children: [renderCopy?.(), jsx("span", {
|
|
9119
|
-
className: css$
|
|
9856
|
+
className: css$33.stringField,
|
|
9120
9857
|
"data-expanded": expanded,
|
|
9121
9858
|
children: jsxs("span", {
|
|
9122
9859
|
ref: contentRef,
|
|
9123
9860
|
id: contentsId,
|
|
9124
|
-
className: css$
|
|
9861
|
+
className: css$33.stringText,
|
|
9125
9862
|
children: [
|
|
9126
9863
|
truncated && jsx("span", {
|
|
9127
|
-
className: css$
|
|
9864
|
+
className: css$33.stringToggleSlot,
|
|
9128
9865
|
children: jsxs("button", {
|
|
9129
9866
|
type: "button",
|
|
9130
|
-
className: css$
|
|
9867
|
+
className: css$33.stringToggle,
|
|
9131
9868
|
"aria-label": labels.expandNode,
|
|
9132
9869
|
"aria-expanded": false,
|
|
9133
9870
|
"aria-controls": contentsId,
|
|
@@ -9142,12 +9879,12 @@ function JsonString({ collapsedStringLines, stringWrapping, field, labels, lastE
|
|
|
9142
9879
|
})
|
|
9143
9880
|
}),
|
|
9144
9881
|
field !== void 0 && jsxs("span", {
|
|
9145
|
-
className: css$
|
|
9882
|
+
className: css$33.label,
|
|
9146
9883
|
children: [fieldText(field), ":"]
|
|
9147
9884
|
}),
|
|
9148
9885
|
primitiveValue(value),
|
|
9149
9886
|
!lastElement && jsx("span", {
|
|
9150
|
-
className: css$
|
|
9887
|
+
className: css$33.punctuation,
|
|
9151
9888
|
children: ","
|
|
9152
9889
|
})
|
|
9153
9890
|
]
|
|
@@ -9178,7 +9915,7 @@ function JsonTreeNode({ collapsedStringLines, stringWrapping, field, initialExpa
|
|
|
9178
9915
|
}
|
|
9179
9916
|
};
|
|
9180
9917
|
const row = (children, ariaExpanded) => jsxs("div", {
|
|
9181
|
-
className: css$
|
|
9918
|
+
className: css$33.row,
|
|
9182
9919
|
role: "treeitem",
|
|
9183
9920
|
"aria-expanded": ariaExpanded,
|
|
9184
9921
|
onMouseOver: (event) => {
|
|
@@ -9213,7 +9950,7 @@ function JsonTreeNode({ collapsedStringLines, stringWrapping, field, initialExpa
|
|
|
9213
9950
|
}),
|
|
9214
9951
|
primitiveValue(value),
|
|
9215
9952
|
!lastElement && jsx("span", {
|
|
9216
|
-
className: css$
|
|
9953
|
+
className: css$33.punctuation,
|
|
9217
9954
|
children: ","
|
|
9218
9955
|
})
|
|
9219
9956
|
] }));
|
|
@@ -9225,22 +9962,22 @@ function JsonTreeNode({ collapsedStringLines, stringWrapping, field, initialExpa
|
|
|
9225
9962
|
onToggle: toggle
|
|
9226
9963
|
}),
|
|
9227
9964
|
jsx("span", {
|
|
9228
|
-
className: css$
|
|
9965
|
+
className: css$33.punctuation,
|
|
9229
9966
|
children: open
|
|
9230
9967
|
}),
|
|
9231
9968
|
jsx("span", {
|
|
9232
|
-
className: css$
|
|
9969
|
+
className: css$33.punctuation,
|
|
9233
9970
|
children: close
|
|
9234
9971
|
}),
|
|
9235
9972
|
!lastElement && jsx("span", {
|
|
9236
|
-
className: css$
|
|
9973
|
+
className: css$33.punctuation,
|
|
9237
9974
|
children: ","
|
|
9238
9975
|
})
|
|
9239
9976
|
] }));
|
|
9240
9977
|
return row(jsxs(Fragment, { children: [
|
|
9241
9978
|
jsx("span", {
|
|
9242
9979
|
ref: expanderRef,
|
|
9243
|
-
className: clsx(css$
|
|
9980
|
+
className: clsx(css$33.expander, expanded ? css$33.collapseIcon : css$33.expandIcon),
|
|
9244
9981
|
"data-json-expander": true,
|
|
9245
9982
|
role: "button",
|
|
9246
9983
|
"aria-label": expanded ? labels.collapseNode : labels.expandNode,
|
|
@@ -9254,7 +9991,7 @@ function JsonTreeNode({ collapsedStringLines, stringWrapping, field, initialExpa
|
|
|
9254
9991
|
onKeyDown: onExpanderKeyDown
|
|
9255
9992
|
}),
|
|
9256
9993
|
jsxs("span", {
|
|
9257
|
-
className: css$
|
|
9994
|
+
className: css$33.summary,
|
|
9258
9995
|
children: [
|
|
9259
9996
|
jsx(NodeField, {
|
|
9260
9997
|
field,
|
|
@@ -9262,11 +9999,11 @@ function JsonTreeNode({ collapsedStringLines, stringWrapping, field, initialExpa
|
|
|
9262
9999
|
onToggle: toggle
|
|
9263
10000
|
}),
|
|
9264
10001
|
jsx("span", {
|
|
9265
|
-
className: css$
|
|
10002
|
+
className: css$33.preview,
|
|
9266
10003
|
children: previewValue(value, 0)
|
|
9267
10004
|
}),
|
|
9268
10005
|
!lastElement && jsx("span", {
|
|
9269
|
-
className: css$
|
|
10006
|
+
className: css$33.punctuation,
|
|
9270
10007
|
children: ","
|
|
9271
10008
|
})
|
|
9272
10009
|
]
|
|
@@ -9274,7 +10011,7 @@ function JsonTreeNode({ collapsedStringLines, stringWrapping, field, initialExpa
|
|
|
9274
10011
|
expanded && jsx("ul", {
|
|
9275
10012
|
id: contentsId,
|
|
9276
10013
|
role: "group",
|
|
9277
|
-
className: css$
|
|
10014
|
+
className: css$33.children,
|
|
9278
10015
|
children: entries.map(([key, item], index) => jsx(JsonTreeNode, {
|
|
9279
10016
|
collapsedStringLines,
|
|
9280
10017
|
stringWrapping,
|
|
@@ -9406,17 +10143,17 @@ function JsonTree({ data, label, className, collapsedStringLines = 3, stringWrap
|
|
|
9406
10143
|
onClose: clearCopyTarget
|
|
9407
10144
|
}) : void 0;
|
|
9408
10145
|
return jsx("div", {
|
|
9409
|
-
className: clsx(css$
|
|
10146
|
+
className: clsx(css$33.root, className),
|
|
9410
10147
|
style: { "--json-tree-collapsed-lines": collapsedStringLines },
|
|
9411
10148
|
onMouseOver: handleRootMouseOver,
|
|
9412
10149
|
onMouseLeave: () => {
|
|
9413
10150
|
if (!copyStore.get()?.menuOpen) clearCopyTarget();
|
|
9414
10151
|
},
|
|
9415
10152
|
children: expandTopLevel ? jsxs("div", {
|
|
9416
|
-
className: css$
|
|
10153
|
+
className: css$33.expandedTopLevel,
|
|
9417
10154
|
children: [
|
|
9418
10155
|
jsxs("div", {
|
|
9419
|
-
className: clsx(css$
|
|
10156
|
+
className: clsx(css$33.row, css$33.topLevelBracket),
|
|
9420
10157
|
"data-json-root-row": true,
|
|
9421
10158
|
onMouseOver: (event) => {
|
|
9422
10159
|
event.stopPropagation();
|
|
@@ -9429,13 +10166,13 @@ function JsonTree({ data, label, className, collapsedStringLines = 3, stringWrap
|
|
|
9429
10166
|
path: [],
|
|
9430
10167
|
value: data
|
|
9431
10168
|
}), jsx("span", {
|
|
9432
|
-
className: css$
|
|
10169
|
+
className: css$33.punctuation,
|
|
9433
10170
|
children: rootOpen
|
|
9434
10171
|
})]
|
|
9435
10172
|
}),
|
|
9436
10173
|
jsx("div", {
|
|
9437
10174
|
"aria-label": label,
|
|
9438
|
-
className: clsx(css$
|
|
10175
|
+
className: clsx(css$33.container, css$33.expandedTopLevelContainer),
|
|
9439
10176
|
role: "tree",
|
|
9440
10177
|
children: rootEntries.map(([key, value], index) => jsx(JsonTreeNode, {
|
|
9441
10178
|
collapsedStringLines,
|
|
@@ -9453,16 +10190,16 @@ function JsonTree({ data, label, className, collapsedStringLines = 3, stringWrap
|
|
|
9453
10190
|
}, key))
|
|
9454
10191
|
}),
|
|
9455
10192
|
jsx("div", {
|
|
9456
|
-
className: clsx(css$
|
|
10193
|
+
className: clsx(css$33.row, css$33.topLevelBracket),
|
|
9457
10194
|
children: jsx("span", {
|
|
9458
|
-
className: css$
|
|
10195
|
+
className: css$33.punctuation,
|
|
9459
10196
|
children: rootClose
|
|
9460
10197
|
})
|
|
9461
10198
|
})
|
|
9462
10199
|
]
|
|
9463
10200
|
}) : jsx("div", {
|
|
9464
10201
|
"aria-label": label,
|
|
9465
|
-
className: css$
|
|
10202
|
+
className: css$33.container,
|
|
9466
10203
|
role: "tree",
|
|
9467
10204
|
children: jsx(JsonTreeNode, {
|
|
9468
10205
|
collapsedStringLines,
|
|
@@ -9987,7 +10724,7 @@ function TerminalBlock({ command, cwd, home, output, exitCode, signal, running =
|
|
|
9987
10724
|
return parsed.length > 1 && last !== void 0 && last.every((span) => span.text === "") ? parsed.slice(0, -1) : parsed;
|
|
9988
10725
|
}, [text]);
|
|
9989
10726
|
const [expanded, setExpanded] = useState(false);
|
|
9990
|
-
const { copied, onCopy } =
|
|
10727
|
+
const { copied, onCopy } = useClipboardCopy(text);
|
|
9991
10728
|
const onToggle = useCallback(() => {
|
|
9992
10729
|
setExpanded((value) => !value);
|
|
9993
10730
|
}, []);
|
|
@@ -10001,31 +10738,31 @@ function TerminalBlock({ command, cwd, home, output, exitCode, signal, running =
|
|
|
10001
10738
|
const { hidden, capped, headLines, tailLines } = headTailCap(lines.length, maxLines, expanded);
|
|
10002
10739
|
const body = !background && (!running || !empty);
|
|
10003
10740
|
return jsxs("div", {
|
|
10004
|
-
className: clsx(css$
|
|
10741
|
+
className: clsx(css$34.block, className),
|
|
10005
10742
|
"data-terminal": "",
|
|
10006
10743
|
"data-running": running ? "" : void 0,
|
|
10007
10744
|
"data-body": body ? "" : void 0,
|
|
10008
10745
|
children: [jsxs("div", {
|
|
10009
|
-
className: css$
|
|
10746
|
+
className: css$34.header,
|
|
10010
10747
|
children: [
|
|
10011
10748
|
jsxs("div", {
|
|
10012
|
-
className: css$
|
|
10749
|
+
className: css$34.prompt,
|
|
10013
10750
|
children: [showState && jsx("span", {
|
|
10014
|
-
className: css$
|
|
10751
|
+
className: css$34.runStateLabel,
|
|
10015
10752
|
children: state.label
|
|
10016
10753
|
}), commandLines.map((line, index) => jsxs("div", {
|
|
10017
|
-
className: css$
|
|
10754
|
+
className: css$34.promptLine,
|
|
10018
10755
|
children: [
|
|
10019
10756
|
index === 0 && showState && jsx(StateDot, {
|
|
10020
10757
|
state: state.state,
|
|
10021
|
-
className: css$
|
|
10758
|
+
className: css$34.runState
|
|
10022
10759
|
}),
|
|
10023
10760
|
jsx("span", {
|
|
10024
|
-
className: css$
|
|
10761
|
+
className: css$34.cwd,
|
|
10025
10762
|
children: index > 0 || cwd === void 0 ? "$" : promptLabel(cwd, home)
|
|
10026
10763
|
}),
|
|
10027
10764
|
jsx(CommandText, {
|
|
10028
|
-
className: css$
|
|
10765
|
+
className: css$34.command,
|
|
10029
10766
|
text: line,
|
|
10030
10767
|
label: copy.commandLine(index + 1)
|
|
10031
10768
|
})
|
|
@@ -10033,36 +10770,36 @@ function TerminalBlock({ command, cwd, home, output, exitCode, signal, running =
|
|
|
10033
10770
|
}, index))]
|
|
10034
10771
|
}),
|
|
10035
10772
|
status !== void 0 && jsx(Pill, {
|
|
10036
|
-
className: css$
|
|
10773
|
+
className: css$34.status,
|
|
10037
10774
|
children: status
|
|
10038
10775
|
}),
|
|
10039
10776
|
!background && !running && !empty && jsx("button", {
|
|
10040
10777
|
type: "button",
|
|
10041
|
-
className: css$
|
|
10778
|
+
className: css$34.copyButton,
|
|
10042
10779
|
onClick: onCopy,
|
|
10043
10780
|
children: copied ? copy.copied : copy.copy
|
|
10044
10781
|
})
|
|
10045
10782
|
]
|
|
10046
10783
|
}), body && (empty ? jsx("div", {
|
|
10047
|
-
className: css$
|
|
10784
|
+
className: css$34.empty,
|
|
10048
10785
|
children: copy.noOutput
|
|
10049
10786
|
}) : jsxs("div", {
|
|
10050
|
-
className: css$
|
|
10787
|
+
className: css$34.output,
|
|
10051
10788
|
children: [
|
|
10052
10789
|
(capped ? lines.slice(0, headLines) : lines).map((line, index) => jsx("div", {
|
|
10053
|
-
className: css$
|
|
10790
|
+
className: css$34.line,
|
|
10054
10791
|
children: renderLine$1(line)
|
|
10055
10792
|
}, index)),
|
|
10056
10793
|
hidden > 0 && jsx("button", {
|
|
10057
10794
|
type: "button",
|
|
10058
|
-
className: css$
|
|
10795
|
+
className: css$34.expand,
|
|
10059
10796
|
"aria-expanded": expanded,
|
|
10060
10797
|
"aria-label": expanded ? copy.collapseAria : copy.expandAria(hidden),
|
|
10061
10798
|
onClick: onToggle,
|
|
10062
10799
|
children: expanded ? copy.collapse : copy.expand(hidden)
|
|
10063
10800
|
}),
|
|
10064
10801
|
capped && lines.slice(lines.length - tailLines).map((line, index) => jsx("div", {
|
|
10065
|
-
className: css$
|
|
10802
|
+
className: css$34.line,
|
|
10066
10803
|
children: renderLine$1(line)
|
|
10067
10804
|
}, index))
|
|
10068
10805
|
]
|
|
@@ -10650,51 +11387,51 @@ function ReadBlock({ label, labels, lines, totalLines, lang, maxLines = 16, clas
|
|
|
10650
11387
|
const tailLines = maxLines - headLines;
|
|
10651
11388
|
const windowed = lines.length < totalLines;
|
|
10652
11389
|
const rows = (slice) => slice.map(([line, spans]) => jsxs("div", {
|
|
10653
|
-
className: css$
|
|
11390
|
+
className: css$35.line,
|
|
10654
11391
|
children: [jsx("span", {
|
|
10655
|
-
className: css$
|
|
11392
|
+
className: css$35.gutter,
|
|
10656
11393
|
"aria-hidden": true,
|
|
10657
11394
|
children: line.number
|
|
10658
11395
|
}), jsx("span", {
|
|
10659
|
-
className: css$
|
|
11396
|
+
className: css$35.content,
|
|
10660
11397
|
children: spans === void 0 ? line.text : renderSpans(spans)
|
|
10661
11398
|
})]
|
|
10662
11399
|
}, line.number));
|
|
10663
11400
|
const paired = lines.map((line, index) => [line, highlighted?.[index]]);
|
|
10664
11401
|
return jsxs("div", {
|
|
10665
11402
|
ref: rootRef,
|
|
10666
|
-
className: clsx(css$
|
|
11403
|
+
className: clsx(css$35.block, className),
|
|
10667
11404
|
"data-read": "",
|
|
10668
11405
|
children: [jsxs("div", {
|
|
10669
|
-
className: css$
|
|
11406
|
+
className: css$35.banner,
|
|
10670
11407
|
children: [jsx("div", {
|
|
10671
|
-
className: css$
|
|
11408
|
+
className: css$35.label,
|
|
10672
11409
|
children: label ?? ""
|
|
10673
11410
|
}), jsxs("div", {
|
|
10674
|
-
className: css$
|
|
11411
|
+
className: css$35.action,
|
|
10675
11412
|
children: [
|
|
10676
11413
|
windowed && jsx("span", {
|
|
10677
|
-
className: css$
|
|
11414
|
+
className: css$35.count,
|
|
10678
11415
|
children: labels.window(lines.length, totalLines)
|
|
10679
11416
|
}),
|
|
10680
11417
|
jsx("span", {
|
|
10681
|
-
className: css$
|
|
11418
|
+
className: css$35.lang,
|
|
10682
11419
|
children: lang ?? ""
|
|
10683
11420
|
}),
|
|
10684
11421
|
lines.length > 0 && jsx("button", {
|
|
10685
11422
|
type: "button",
|
|
10686
|
-
className: css$
|
|
11423
|
+
className: css$35.copyButton,
|
|
10687
11424
|
onClick: onCopy,
|
|
10688
11425
|
children: copied ? labels.copied : labels.copy
|
|
10689
11426
|
})
|
|
10690
11427
|
]
|
|
10691
11428
|
})]
|
|
10692
11429
|
}), jsxs("div", {
|
|
10693
|
-
className: css$
|
|
11430
|
+
className: css$35.body,
|
|
10694
11431
|
children: [
|
|
10695
11432
|
rows(capped ? paired.slice(0, headLines) : paired),
|
|
10696
11433
|
hidden > 0 && jsx(FoldToggle, {
|
|
10697
|
-
className: css$
|
|
11434
|
+
className: css$35.expand,
|
|
10698
11435
|
expanded,
|
|
10699
11436
|
hidden,
|
|
10700
11437
|
labels,
|
|
@@ -10716,11 +11453,11 @@ function assertNever(value) {
|
|
|
10716
11453
|
}
|
|
10717
11454
|
/** The dim class per row kind (path/gap chrome vs the diff's own +/- colors). */
|
|
10718
11455
|
const ROW_CLASS = {
|
|
10719
|
-
path: css$
|
|
10720
|
-
del: css$
|
|
10721
|
-
add: css$
|
|
10722
|
-
context: css$
|
|
10723
|
-
gap: css$
|
|
11456
|
+
path: css$36.path,
|
|
11457
|
+
del: css$36.del,
|
|
11458
|
+
add: css$36.add,
|
|
11459
|
+
context: css$36.context,
|
|
11460
|
+
gap: css$36.gap
|
|
10724
11461
|
};
|
|
10725
11462
|
/** Bound synchronous edit-graph search; one replacement consumes two edits. */
|
|
10726
11463
|
const MAX_DIFF_EDIT_LENGTH = 256;
|
|
@@ -10858,37 +11595,37 @@ function DiffBlock({ diffs, labels, maxLines = 16, className }) {
|
|
|
10858
11595
|
const head = capped ? rows.slice(0, headLines) : rows;
|
|
10859
11596
|
const tail = capped ? rows.slice(rows.length - tailLines) : [];
|
|
10860
11597
|
return jsxs("div", {
|
|
10861
|
-
className: clsx(css$
|
|
11598
|
+
className: clsx(css$36.block, className),
|
|
10862
11599
|
"data-diff": "",
|
|
10863
11600
|
children: [
|
|
10864
11601
|
jsx("button", {
|
|
10865
11602
|
type: "button",
|
|
10866
|
-
className: css$
|
|
11603
|
+
className: css$36.copyButton,
|
|
10867
11604
|
onClick: onCopy,
|
|
10868
11605
|
children: copied ? labels.copied : labels.copy
|
|
10869
11606
|
}),
|
|
10870
11607
|
jsxs("div", {
|
|
10871
|
-
className: css$
|
|
11608
|
+
className: css$36.body,
|
|
10872
11609
|
children: [
|
|
10873
11610
|
head.map((row, index) => jsx("div", {
|
|
10874
|
-
className: clsx(css$
|
|
11611
|
+
className: clsx(css$36.line, ROW_CLASS[row.kind]),
|
|
10875
11612
|
children: row.text
|
|
10876
11613
|
}, index)),
|
|
10877
11614
|
hidden > 0 && jsx(FoldToggle, {
|
|
10878
|
-
className: css$
|
|
11615
|
+
className: css$36.expand,
|
|
10879
11616
|
expanded,
|
|
10880
11617
|
hidden,
|
|
10881
11618
|
labels,
|
|
10882
11619
|
onToggle
|
|
10883
11620
|
}),
|
|
10884
11621
|
tail.map((row, index) => jsx("div", {
|
|
10885
|
-
className: clsx(css$
|
|
11622
|
+
className: clsx(css$36.line, ROW_CLASS[row.kind]),
|
|
10886
11623
|
children: row.text
|
|
10887
11624
|
}, index))
|
|
10888
11625
|
]
|
|
10889
11626
|
}),
|
|
10890
11627
|
jsxs("div", {
|
|
10891
|
-
className: css$
|
|
11628
|
+
className: css$36.footer,
|
|
10892
11629
|
children: [
|
|
10893
11630
|
"└ +",
|
|
10894
11631
|
added,
|
|
@@ -11004,7 +11741,7 @@ function SearchBlock(props) {
|
|
|
11004
11741
|
const rows = toRows(props, collapsed);
|
|
11005
11742
|
const shown = shownCount(props);
|
|
11006
11743
|
const empty = rows.length === 0;
|
|
11007
|
-
const { copied, onCopy } =
|
|
11744
|
+
const { copied, onCopy } = useClipboardCopy(copyText(props));
|
|
11008
11745
|
const onToggle = useCallback(() => {
|
|
11009
11746
|
setExpanded((value) => !value);
|
|
11010
11747
|
}, []);
|
|
@@ -11024,56 +11761,56 @@ function SearchBlock(props) {
|
|
|
11024
11761
|
const tail = tailHeader === void 0 ? naturalTail : naturalTail.slice(1);
|
|
11025
11762
|
const renderRow = (row) => {
|
|
11026
11763
|
if (row.type === "path") return jsx("div", {
|
|
11027
|
-
className: css$
|
|
11764
|
+
className: css$37.line,
|
|
11028
11765
|
children: row.path
|
|
11029
11766
|
});
|
|
11030
11767
|
if (row.type === "match") return jsxs("div", {
|
|
11031
|
-
className: css$
|
|
11768
|
+
className: css$37.line,
|
|
11032
11769
|
children: [jsxs("span", {
|
|
11033
|
-
className: css$
|
|
11770
|
+
className: css$37.lineNumber,
|
|
11034
11771
|
children: [row.lineNumber, ": "]
|
|
11035
11772
|
}), row.line]
|
|
11036
11773
|
});
|
|
11037
11774
|
return jsxs("button", {
|
|
11038
11775
|
type: "button",
|
|
11039
|
-
className: css$
|
|
11776
|
+
className: css$37.fileHeader,
|
|
11040
11777
|
"aria-expanded": !row.collapsed,
|
|
11041
11778
|
onClick: () => {
|
|
11042
11779
|
toggleFile(row.index);
|
|
11043
11780
|
},
|
|
11044
11781
|
children: [jsx("span", {
|
|
11045
|
-
className: css$
|
|
11782
|
+
className: css$37.filePath,
|
|
11046
11783
|
children: row.path
|
|
11047
11784
|
}), jsx("span", {
|
|
11048
|
-
className: css$
|
|
11785
|
+
className: css$37.fileCount,
|
|
11049
11786
|
children: row.count
|
|
11050
11787
|
})]
|
|
11051
11788
|
});
|
|
11052
11789
|
};
|
|
11053
11790
|
return jsxs("div", {
|
|
11054
|
-
className: clsx(css$
|
|
11791
|
+
className: clsx(css$37.block, className),
|
|
11055
11792
|
"data-search": props.kind,
|
|
11056
11793
|
children: [jsxs("div", {
|
|
11057
|
-
className: css$
|
|
11794
|
+
className: css$37.header,
|
|
11058
11795
|
children: [jsx("span", {
|
|
11059
|
-
className: css$
|
|
11796
|
+
className: css$37.summary,
|
|
11060
11797
|
children: summaryText(props, shown, truncated, total)
|
|
11061
11798
|
}), !empty && jsx("button", {
|
|
11062
11799
|
type: "button",
|
|
11063
|
-
className: css$
|
|
11800
|
+
className: css$37.copyButton,
|
|
11064
11801
|
onClick: onCopy,
|
|
11065
11802
|
children: copied ? props.labels.copied : props.labels.copy
|
|
11066
11803
|
})]
|
|
11067
11804
|
}), empty ? jsx("div", {
|
|
11068
|
-
className: css$
|
|
11805
|
+
className: css$37.empty,
|
|
11069
11806
|
children: props.labels.noResults
|
|
11070
11807
|
}) : jsxs("div", {
|
|
11071
|
-
className: css$
|
|
11808
|
+
className: css$37.body,
|
|
11072
11809
|
children: [
|
|
11073
11810
|
head.map((row) => jsx("div", { children: renderRow(row) }, rowKey(row))),
|
|
11074
11811
|
hidden > 0 && jsx("button", {
|
|
11075
11812
|
type: "button",
|
|
11076
|
-
className: css$
|
|
11813
|
+
className: css$37.expand,
|
|
11077
11814
|
"aria-expanded": expanded,
|
|
11078
11815
|
"aria-label": expanded ? props.labels.collapseAria : props.labels.expandAria(hidden),
|
|
11079
11816
|
onClick: onToggle,
|
|
@@ -11469,7 +12206,12 @@ function cjkFriendlyStrong() {
|
|
|
11469
12206
|
}
|
|
11470
12207
|
//#endregion
|
|
11471
12208
|
//#region lib/types/markdown/mathCompatibility.js
|
|
11472
|
-
/**
|
|
12209
|
+
/**
|
|
12210
|
+
* Extend upstream dollar-only math syntax with TeX delimiters while reusing its
|
|
12211
|
+
* token vocabulary. The dollar construct claims every `$$` flow fence, so text
|
|
12212
|
+
* on the opening line is math content instead of the upstream construct's fence
|
|
12213
|
+
* meta, which the renderer does not read.
|
|
12214
|
+
*/
|
|
11473
12215
|
const previousBackslash = function(code) {
|
|
11474
12216
|
if (code !== codes.backslash) return true;
|
|
11475
12217
|
const tail = this.events.at(-1);
|
|
@@ -11570,7 +12312,7 @@ const tokenizeBackslashMathText = function(effects, ok, nok) {
|
|
|
11570
12312
|
}
|
|
11571
12313
|
}
|
|
11572
12314
|
};
|
|
11573
|
-
function createMathFlow(marker, openMarker, closeMarker
|
|
12315
|
+
function createMathFlow(marker, openMarker, closeMarker) {
|
|
11574
12316
|
const tokenize = function(effects, ok, nok) {
|
|
11575
12317
|
const self = this;
|
|
11576
12318
|
let oddBackslashRun = false;
|
|
@@ -11602,7 +12344,7 @@ function createMathFlow(marker, openMarker, closeMarker, multiline) {
|
|
|
11602
12344
|
partial: true,
|
|
11603
12345
|
tokenize: tokenizeClosingFence
|
|
11604
12346
|
}, closed, afterClosingFenceAttempt)(code);
|
|
11605
|
-
if (markdownLineEnding(code)) return
|
|
12347
|
+
if (markdownLineEnding(code)) return self.interrupt ? ok(code) : effects.attempt(nonLazyContinuation, afterContinuation, nok)(code);
|
|
11606
12348
|
return valueStart(code);
|
|
11607
12349
|
}
|
|
11608
12350
|
function afterClosingFenceAttempt(code) {
|
|
@@ -11689,7 +12431,7 @@ function createMathFlow(marker, openMarker, closeMarker, multiline) {
|
|
|
11689
12431
|
};
|
|
11690
12432
|
return {
|
|
11691
12433
|
concrete: true,
|
|
11692
|
-
name: marker === codes.dollarSign ? "
|
|
12434
|
+
name: marker === codes.dollarSign ? "dollarMathFlow" : "backslashMathFlow",
|
|
11693
12435
|
tokenize
|
|
11694
12436
|
};
|
|
11695
12437
|
}
|
|
@@ -11719,26 +12461,66 @@ const backslashMathText = {
|
|
|
11719
12461
|
previous: previousBackslash,
|
|
11720
12462
|
tokenize: tokenizeBackslashMathText
|
|
11721
12463
|
};
|
|
11722
|
-
const backslashMathFlow = createMathFlow(codes.backslash, codes.leftSquareBracket, codes.rightSquareBracket
|
|
11723
|
-
const
|
|
12464
|
+
const backslashMathFlow = createMathFlow(codes.backslash, codes.leftSquareBracket, codes.rightSquareBracket);
|
|
12465
|
+
const dollarMathFlow = createMathFlow(codes.dollarSign, codes.dollarSign, codes.dollarSign);
|
|
11724
12466
|
const backslashMath = {
|
|
11725
12467
|
flow: {
|
|
11726
12468
|
[codes.backslash]: backslashMathFlow,
|
|
11727
|
-
[codes.dollarSign]:
|
|
12469
|
+
[codes.dollarSign]: dollarMathFlow
|
|
11728
12470
|
},
|
|
11729
12471
|
text: { [codes.backslash]: backslashMathText }
|
|
11730
12472
|
};
|
|
11731
12473
|
/**
|
|
11732
|
-
* TeX backslash delimiters and
|
|
11733
|
-
*
|
|
11734
|
-
*
|
|
11735
|
-
*
|
|
12474
|
+
* TeX backslash delimiters and display-dollar blocks as a micromark syntax
|
|
12475
|
+
* extension reusing `micromark-extension-math`'s token vocabulary; the caller
|
|
12476
|
+
* must also register `math()` on the same parse so the emitted tokens compile
|
|
12477
|
+
* to standard math nodes. Both `$$<math>$$` on one line and a `$$<math>` block
|
|
12478
|
+
* closed by a trailing `$$` parse as one math node, and a closing `$$` may sit
|
|
12479
|
+
* at the end of a content line.
|
|
11736
12480
|
* @returns The micromark syntax extension.
|
|
11737
12481
|
*/
|
|
11738
12482
|
function mathCompatibility() {
|
|
11739
12483
|
return backslashMath;
|
|
11740
12484
|
}
|
|
11741
12485
|
//#endregion
|
|
12486
|
+
//#region lib/types/markdown/windows-path-destinations.js
|
|
12487
|
+
/** Keep backslash separators in Markdown destinations written as Windows paths. */
|
|
12488
|
+
/**
|
|
12489
|
+
* A raw destination is a Windows path when it has a backslash and no URL
|
|
12490
|
+
* scheme, and either has no `/` or has a backslash before a letter or digit,
|
|
12491
|
+
* which CommonMark cannot read as an escape. A drive letter is not a scheme.
|
|
12492
|
+
* @param raw - The destination exactly as authored between parentheses or angle brackets.
|
|
12493
|
+
* @returns Whether CommonMark escapes in the destination are Windows separators.
|
|
12494
|
+
*/
|
|
12495
|
+
function isWindowsPath(raw) {
|
|
12496
|
+
return raw.includes("\\") && !/^[a-z][a-z\d+.-]+:/iu.test(raw) && (!raw.includes("/") || /\\[\p{L}\p{N}]/u.test(raw));
|
|
12497
|
+
}
|
|
12498
|
+
/**
|
|
12499
|
+
* Replace the destination of the link, image, or definition being exited.
|
|
12500
|
+
* @param this - Compile context positioned on the destination events.
|
|
12501
|
+
* @param token - The entity token naming the destination.
|
|
12502
|
+
*/
|
|
12503
|
+
function exitDestination(token) {
|
|
12504
|
+
const parsed = this.resume();
|
|
12505
|
+
const raw = this.sliceSerialize(token);
|
|
12506
|
+
const node = this.stack.at(-1);
|
|
12507
|
+
node.url = isWindowsPath(raw) ? decodeString(raw.replaceAll("\\", "\\\\")) : parsed;
|
|
12508
|
+
}
|
|
12509
|
+
/**
|
|
12510
|
+
* CommonMark reads `\.` or `\_` in a destination as an escaped character, so
|
|
12511
|
+
* `D:\repo\.github` would parse as `D:\repo.github`. For a destination that
|
|
12512
|
+
* is a Windows path, this fromMarkdown extension keeps every backslash
|
|
12513
|
+
* literally, including one before punctuation such as `\(`, while character
|
|
12514
|
+
* references such as `&` still decode; other destinations parse unchanged.
|
|
12515
|
+
* @returns The mdast extension to add to `mdastExtensions`.
|
|
12516
|
+
*/
|
|
12517
|
+
function windowsPathDestinations() {
|
|
12518
|
+
return { exit: {
|
|
12519
|
+
resourceDestinationString: exitDestination,
|
|
12520
|
+
definitionDestinationString: exitDestination
|
|
12521
|
+
} };
|
|
12522
|
+
}
|
|
12523
|
+
//#endregion
|
|
11742
12524
|
//#region lib/types/markdown/parse.js
|
|
11743
12525
|
/**
|
|
11744
12526
|
* The markdown renderer's two mdast grammars, one per rendering arm. Each
|
|
@@ -11749,6 +12531,8 @@ function mathCompatibility() {
|
|
|
11749
12531
|
* begin a math construct (a `$$` block is a paragraph while streaming and a
|
|
11750
12532
|
* math block once settled, by design).
|
|
11751
12533
|
*/
|
|
12534
|
+
/** GFM strikethrough requires `~~`; a single `~` stays literal, as in ranges such as `10~20`. */
|
|
12535
|
+
const gfmOptions = { singleTilde: false };
|
|
11752
12536
|
/**
|
|
11753
12537
|
* Parse GFM markdown (the streaming arm's grammar: no math, so incomplete
|
|
11754
12538
|
* TeX never flashes KaTeX errors mid-stream).
|
|
@@ -11757,8 +12541,8 @@ function mathCompatibility() {
|
|
|
11757
12541
|
*/
|
|
11758
12542
|
function parseGfm(text) {
|
|
11759
12543
|
return recoverLocalImages(fromMarkdown(text, {
|
|
11760
|
-
extensions: [gfm(), cjkFriendlyStrong()],
|
|
11761
|
-
mdastExtensions: [gfmFromMarkdown()]
|
|
12544
|
+
extensions: [gfm(gfmOptions), cjkFriendlyStrong()],
|
|
12545
|
+
mdastExtensions: [gfmFromMarkdown(), windowsPathDestinations()]
|
|
11762
12546
|
}), text);
|
|
11763
12547
|
}
|
|
11764
12548
|
/**
|
|
@@ -11770,12 +12554,16 @@ function parseGfm(text) {
|
|
|
11770
12554
|
function parseGfmWithMath(text) {
|
|
11771
12555
|
return recoverLocalImages(fromMarkdown(text, {
|
|
11772
12556
|
extensions: [
|
|
11773
|
-
gfm(),
|
|
12557
|
+
gfm(gfmOptions),
|
|
11774
12558
|
cjkFriendlyStrong(),
|
|
11775
|
-
|
|
11776
|
-
|
|
12559
|
+
math(),
|
|
12560
|
+
mathCompatibility()
|
|
11777
12561
|
],
|
|
11778
|
-
mdastExtensions: [
|
|
12562
|
+
mdastExtensions: [
|
|
12563
|
+
gfmFromMarkdown(),
|
|
12564
|
+
mathFromMarkdown(),
|
|
12565
|
+
windowsPathDestinations()
|
|
12566
|
+
]
|
|
11779
12567
|
}), text);
|
|
11780
12568
|
}
|
|
11781
12569
|
//#endregion
|
|
@@ -11807,7 +12595,8 @@ function renderLine(line, index) {
|
|
|
11807
12595
|
}
|
|
11808
12596
|
function CodeBlock({ code, lang, streaming, className, contentRef, lineNumbers = false, showHeader = true, copyLabel, copiedLabel }) {
|
|
11809
12597
|
const trimmed = code.endsWith("\n") ? code.slice(0, -1) : code;
|
|
11810
|
-
const
|
|
12598
|
+
const plainLines = trimmed.split("\n");
|
|
12599
|
+
const sourceLines = lineNumbers ? plainLines : void 0;
|
|
11811
12600
|
const rootRef = useRef(null);
|
|
11812
12601
|
const highlighting = useViewportHighlighting(rootRef, lang);
|
|
11813
12602
|
const loaded = useSyncExternalStore(subscribeGrammarLoaded, grammarLoadCount, grammarLoadCount);
|
|
@@ -11901,30 +12690,30 @@ function CodeBlock({ code, lang, streaming, className, contentRef, lineNumbers =
|
|
|
11901
12690
|
});
|
|
11902
12691
|
}, [copied, trimmed]);
|
|
11903
12692
|
const body = streamedBody !== void 0 ? streamedBody : html === void 0 ? jsx("pre", {
|
|
11904
|
-
className: css$
|
|
11905
|
-
children: jsx("code", { children:
|
|
12693
|
+
className: css$38.plain,
|
|
12694
|
+
children: jsx("code", { children: plainLines.map((line, index) => jsxs(Fragment$1, { children: [index > 0 && "\n", jsx("span", {
|
|
11906
12695
|
className: "line",
|
|
11907
12696
|
children: line
|
|
11908
12697
|
})] }, index)) })
|
|
11909
12698
|
}) : jsx("div", { dangerouslySetInnerHTML: { __html: html } });
|
|
11910
12699
|
return jsxs("div", {
|
|
11911
12700
|
ref: rootRef,
|
|
11912
|
-
className: clsx(css$
|
|
12701
|
+
className: clsx(css$38.block, "md-code-block", lineNumbers && css$38.numbered, className),
|
|
11913
12702
|
"data-line-numbers": lineNumbers || void 0,
|
|
11914
12703
|
style: sourceLines === void 0 ? void 0 : { "--dsl-code-block-line-number-width": `${Math.max(2, String(sourceLines.length).length)}ch` },
|
|
11915
12704
|
children: [showHeader && jsx("div", {
|
|
11916
|
-
className: css$
|
|
12705
|
+
className: css$38.bannerWrap,
|
|
11917
12706
|
children: jsxs("div", {
|
|
11918
|
-
className: css$
|
|
12707
|
+
className: css$38.banner,
|
|
11919
12708
|
"data-code-block-banner": true,
|
|
11920
12709
|
children: [jsx("div", {
|
|
11921
|
-
className: css$
|
|
12710
|
+
className: css$38.infostring,
|
|
11922
12711
|
children: lang ?? ""
|
|
11923
12712
|
}), jsx("div", {
|
|
11924
|
-
className: css$
|
|
12713
|
+
className: css$38.action,
|
|
11925
12714
|
children: jsx("button", {
|
|
11926
12715
|
type: "button",
|
|
11927
|
-
className: css$
|
|
12716
|
+
className: css$38.copyButton,
|
|
11928
12717
|
onClick: onCopy,
|
|
11929
12718
|
children: copied ? copiedLabel : copyLabel
|
|
11930
12719
|
})
|
|
@@ -11932,7 +12721,7 @@ function CodeBlock({ code, lang, streaming, className, contentRef, lineNumbers =
|
|
|
11932
12721
|
})
|
|
11933
12722
|
}), jsx("div", {
|
|
11934
12723
|
ref: contentRef,
|
|
11935
|
-
className: css$
|
|
12724
|
+
className: css$38.content,
|
|
11936
12725
|
"data-code-block-content": true,
|
|
11937
12726
|
children: body
|
|
11938
12727
|
})]
|
|
@@ -12083,15 +12872,15 @@ function MermaidDiagram({ code, copyLabel, copiedLabel, diagramLabel, enlargedLa
|
|
|
12083
12872
|
};
|
|
12084
12873
|
}, [code, domId]);
|
|
12085
12874
|
if (state.status === "loading") return jsx("div", {
|
|
12086
|
-
className: css$
|
|
12875
|
+
className: css$39.stage,
|
|
12087
12876
|
"data-mermaid-loading": true,
|
|
12088
12877
|
"aria-hidden": "true"
|
|
12089
12878
|
});
|
|
12090
12879
|
if (state.status === "error") return jsxs("div", {
|
|
12091
|
-
className: css$
|
|
12880
|
+
className: css$39.error,
|
|
12092
12881
|
role: "alert",
|
|
12093
12882
|
children: [jsx("span", {
|
|
12094
|
-
className: css$
|
|
12883
|
+
className: css$39.errorLine,
|
|
12095
12884
|
children: state.message
|
|
12096
12885
|
}), jsx(CodeBlock, {
|
|
12097
12886
|
code: `${code}\n`,
|
|
@@ -12100,7 +12889,7 @@ function MermaidDiagram({ code, copyLabel, copiedLabel, diagramLabel, enlargedLa
|
|
|
12100
12889
|
})]
|
|
12101
12890
|
});
|
|
12102
12891
|
const stage = jsx("div", {
|
|
12103
|
-
className: css$
|
|
12892
|
+
className: css$39.stage,
|
|
12104
12893
|
"data-mermaid-diagram": true,
|
|
12105
12894
|
dangerouslySetInnerHTML: { __html: state.svg },
|
|
12106
12895
|
onClick: () => {
|
|
@@ -12115,13 +12904,13 @@ function MermaidDiagram({ code, copyLabel, copiedLabel, diagramLabel, enlargedLa
|
|
|
12115
12904
|
});
|
|
12116
12905
|
if (zoom === null) return stage;
|
|
12117
12906
|
return jsxs(Fragment, { children: [stage, createPortal(jsx("div", {
|
|
12118
|
-
className: css$
|
|
12907
|
+
className: css$39.zoomOverlay,
|
|
12119
12908
|
onClick: () => {
|
|
12120
12909
|
setZoom(null);
|
|
12121
12910
|
},
|
|
12122
12911
|
role: "presentation",
|
|
12123
12912
|
children: jsx("div", {
|
|
12124
|
-
className: css$
|
|
12913
|
+
className: css$39.zoomStage,
|
|
12125
12914
|
dangerouslySetInnerHTML: { __html: zoom },
|
|
12126
12915
|
onClick: (event) => {
|
|
12127
12916
|
event.stopPropagation();
|
|
@@ -12135,11 +12924,23 @@ function MermaidDiagram({ code, copyLabel, copiedLabel, diagramLabel, enlargedLa
|
|
|
12135
12924
|
//#region lib/types/markdown/file-link.js
|
|
12136
12925
|
/** Local Markdown destinations accepted by the file-preview callback. */
|
|
12137
12926
|
/**
|
|
12138
|
-
*
|
|
12139
|
-
*
|
|
12927
|
+
* Recognize a relative, rooted, or absolute Windows path before URL parsing.
|
|
12928
|
+
* @param value - A decoded or URI-normalized destination.
|
|
12929
|
+
* @returns Whether a filesystem prefix makes the destination unambiguous.
|
|
12930
|
+
*/
|
|
12931
|
+
function isExplicitFilePath(value) {
|
|
12932
|
+
return /^(?:[\\/]|\.{1,2}[\\/]|[a-z]:[\\/])/i.test(value);
|
|
12933
|
+
}
|
|
12934
|
+
/**
|
|
12935
|
+
* Decode a file destination with a fragment, line range, or line/column suffix.
|
|
12936
|
+
* Explicit relative, slash/backslash-rooted, and Windows drive paths take
|
|
12937
|
+
* precedence over URLs. Other parseable absolute URLs are not file links.
|
|
12938
|
+
* Fragments take precedence over colon suffixes; invalid locations open the
|
|
12939
|
+
* file without selecting a line. Columns are consumed but not selected.
|
|
12940
|
+
* Percent-encode literal `?`, `#`, and numeric colon suffixes in filenames.
|
|
12140
12941
|
* @param value - Parsed Markdown link destination.
|
|
12141
12942
|
* @returns A local path and optional first line, or undefined for URLs,
|
|
12142
|
-
* fragment-only links, queries, malformed escapes, or
|
|
12943
|
+
* fragment-only links, queries, malformed escapes, or control characters.
|
|
12143
12944
|
*/
|
|
12144
12945
|
function parseFileLink(value) {
|
|
12145
12946
|
const hash = value.indexOf("#");
|
|
@@ -12151,14 +12952,16 @@ function parseFileLink(value) {
|
|
|
12151
12952
|
} catch (_error) {
|
|
12152
12953
|
return;
|
|
12153
12954
|
}
|
|
12154
|
-
if (path.length === 0 || /[\u0000-\u001f\u007f]/.test(path)
|
|
12155
|
-
if (
|
|
12156
|
-
const
|
|
12157
|
-
|
|
12158
|
-
if (
|
|
12955
|
+
if (path.length === 0 || /[\u0000-\u001f\u007f]/.test(path)) return void 0;
|
|
12956
|
+
if (!isExplicitFilePath(path) && URL.canParse(path)) return void 0;
|
|
12957
|
+
const suffix = hash < 0 ? /:(\d+)(?::\d+|-(\d+))?$/.exec(destination) : null;
|
|
12958
|
+
if (suffix !== null) path = path.slice(0, -suffix[0].length);
|
|
12959
|
+
if (path.length === 0) return void 0;
|
|
12960
|
+
const match = suffix ?? (hash < 0 ? null : /^L(\d+)(?:-L(\d+))?$/.exec(value.slice(hash + 1)));
|
|
12961
|
+
if (match === null) return { path };
|
|
12159
12962
|
const line = Number(match[1]);
|
|
12160
12963
|
const end = match[2] === void 0 ? line : Number(match[2]);
|
|
12161
|
-
if (!Number.isSafeInteger(line) || !Number.isSafeInteger(end) || end < line) return
|
|
12964
|
+
if (!Number.isSafeInteger(line) || !Number.isSafeInteger(end) || line < 1 || end < line) return { path };
|
|
12162
12965
|
return {
|
|
12163
12966
|
path,
|
|
12164
12967
|
line
|
|
@@ -12255,15 +13058,17 @@ const MarkdownDelegateContext = createContext({});
|
|
|
12255
13058
|
* @param props - Child tree and its file and HTTP(S) link handlers.
|
|
12256
13059
|
* @returns the scoped child tree.
|
|
12257
13060
|
*/
|
|
12258
|
-
function MarkdownDelegateProvider({ children, openExternalLink, openFile, fileImages }) {
|
|
13061
|
+
function MarkdownDelegateProvider({ children, openExternalLink, openFile, fileImages, getTriggerProps }) {
|
|
12259
13062
|
const delegate = useMemo(() => ({
|
|
12260
13063
|
openExternalLink,
|
|
12261
13064
|
openFile,
|
|
12262
|
-
fileImages
|
|
13065
|
+
fileImages,
|
|
13066
|
+
getTriggerProps
|
|
12263
13067
|
}), [
|
|
12264
13068
|
openExternalLink,
|
|
12265
13069
|
openFile,
|
|
12266
|
-
fileImages
|
|
13070
|
+
fileImages,
|
|
13071
|
+
getTriggerProps
|
|
12267
13072
|
]);
|
|
12268
13073
|
return jsx(MarkdownDelegateContext.Provider, {
|
|
12269
13074
|
value: delegate,
|
|
@@ -12292,13 +13097,14 @@ function useMarkdownDelegate() {
|
|
|
12292
13097
|
* @param props.onClose - dismiss callback owned by the opener.
|
|
12293
13098
|
* @returns the modal preview dialog.
|
|
12294
13099
|
*/
|
|
12295
|
-
function ImageLightbox({ src, alt, labels, onClose }) {
|
|
13100
|
+
function ImageLightbox({ src, alt, labels, onClose, contextMenuProps }) {
|
|
12296
13101
|
const closeRef = useRef(null);
|
|
12297
13102
|
const restoreRef = useRef(null);
|
|
12298
13103
|
useEffect(() => {
|
|
12299
13104
|
restoreRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
12300
13105
|
closeRef.current?.focus();
|
|
12301
13106
|
const onKeyDown = (event) => {
|
|
13107
|
+
if (event.defaultPrevented || document.activeElement?.closest("[role=\"menu\"]")) return;
|
|
12302
13108
|
if (event.key === "Escape") {
|
|
12303
13109
|
event.stopPropagation();
|
|
12304
13110
|
onClose();
|
|
@@ -12315,27 +13121,28 @@ function ImageLightbox({ src, alt, labels, onClose }) {
|
|
|
12315
13121
|
};
|
|
12316
13122
|
}, [onClose]);
|
|
12317
13123
|
return createPortal(jsx("div", {
|
|
12318
|
-
className: css$
|
|
13124
|
+
className: css$40.backdrop,
|
|
12319
13125
|
role: "dialog",
|
|
12320
13126
|
"aria-modal": "true",
|
|
12321
13127
|
"aria-label": labels.dialog,
|
|
12322
13128
|
children: jsxs("div", {
|
|
12323
|
-
className: css$
|
|
13129
|
+
className: css$40.stage,
|
|
12324
13130
|
children: [
|
|
12325
13131
|
jsx("div", {
|
|
12326
|
-
className: css$
|
|
13132
|
+
className: css$40.mask,
|
|
12327
13133
|
"aria-hidden": "true",
|
|
12328
13134
|
onMouseDown: onClose
|
|
12329
13135
|
}),
|
|
12330
13136
|
jsx("img", {
|
|
12331
|
-
className: css$
|
|
13137
|
+
className: css$40.image,
|
|
12332
13138
|
src,
|
|
12333
|
-
alt
|
|
13139
|
+
alt,
|
|
13140
|
+
...contextMenuProps
|
|
12334
13141
|
}),
|
|
12335
13142
|
jsx("button", {
|
|
12336
13143
|
ref: closeRef,
|
|
12337
13144
|
type: "button",
|
|
12338
|
-
className: css$
|
|
13145
|
+
className: css$40.close,
|
|
12339
13146
|
"aria-label": labels.close,
|
|
12340
13147
|
onClick: onClose,
|
|
12341
13148
|
children: jsx(IconCloseOutlineRegular, { size: 16 })
|
|
@@ -12363,14 +13170,14 @@ function ImagePreview({ src, alt, loadingLabel, failedLabel }) {
|
|
|
12363
13170
|
function Preview({ src, alt, loadingLabel, failedLabel }) {
|
|
12364
13171
|
const [state, setState] = useState("loading");
|
|
12365
13172
|
return jsxs("span", {
|
|
12366
|
-
className: css$
|
|
13173
|
+
className: css$41.frame,
|
|
12367
13174
|
children: [state !== "failed" && jsx("img", {
|
|
12368
13175
|
src,
|
|
12369
13176
|
alt,
|
|
12370
13177
|
loading: "lazy",
|
|
12371
13178
|
decoding: "async",
|
|
12372
13179
|
referrerPolicy: "no-referrer",
|
|
12373
|
-
className: css$
|
|
13180
|
+
className: css$41.image,
|
|
12374
13181
|
"data-ready": state === "ready" || void 0,
|
|
12375
13182
|
onLoad: () => {
|
|
12376
13183
|
setState("ready");
|
|
@@ -12379,7 +13186,7 @@ function Preview({ src, alt, loadingLabel, failedLabel }) {
|
|
|
12379
13186
|
setState("failed");
|
|
12380
13187
|
}
|
|
12381
13188
|
}), state !== "ready" && jsxs("span", {
|
|
12382
|
-
className: css$
|
|
13189
|
+
className: css$41.status,
|
|
12383
13190
|
role: "status",
|
|
12384
13191
|
children: [state === "loading" && jsx(IconLoadingOutlineRegular, { size: 16 }), jsx("span", { children: state === "loading" ? loadingLabel : failedLabel })]
|
|
12385
13192
|
})]
|
|
@@ -12440,13 +13247,31 @@ function vocabularyImageUrl(url) {
|
|
|
12440
13247
|
* caller.
|
|
12441
13248
|
* @param url - The authored markdown destination.
|
|
12442
13249
|
* @param pathImages - Rewriting vocabulary, when the render pass has one.
|
|
13250
|
+
* @param load - The mounting image's load number, which keeps a rewritten URL distinct from an earlier render's.
|
|
12443
13251
|
* @returns The displayable image URL, or undefined.
|
|
12444
13252
|
*/
|
|
12445
|
-
function imageSource(url, pathImages) {
|
|
13253
|
+
function imageSource(url, pathImages, load) {
|
|
12446
13254
|
const remote = remoteImageUrl(sanitizeUrl(normalizeUri(url)));
|
|
12447
13255
|
if (remote !== void 0) return remote;
|
|
12448
13256
|
const rewritten = pathImages?.resolve(url);
|
|
12449
|
-
|
|
13257
|
+
const allowed = rewritten === void 0 ? void 0 : vocabularyImageUrl(rewritten);
|
|
13258
|
+
return allowed === void 0 ? void 0 : freshImageUrl(allowed, load);
|
|
13259
|
+
}
|
|
13260
|
+
let imageLoads = 0;
|
|
13261
|
+
const nextImageLoad = () => ++imageLoads;
|
|
13262
|
+
/**
|
|
13263
|
+
* Tag a rewritten local image URL with its mount's load number. Browsers reuse
|
|
13264
|
+
* a same-URL image within one document despite `no-store`, so without the tag
|
|
13265
|
+
* a newly rendered image of a rewritten file would show the earlier bytes.
|
|
13266
|
+
* @param url - Absolute rewritten image URL; data and blob URLs stay unchanged.
|
|
13267
|
+
* @param load - Number allocated once per mounted image.
|
|
13268
|
+
* @returns The URL with a `load` query parameter.
|
|
13269
|
+
*/
|
|
13270
|
+
function freshImageUrl(url, load) {
|
|
13271
|
+
const fresh = URL.parse(url);
|
|
13272
|
+
if (fresh === null || fresh.protocol === "data:" || fresh.protocol === "blob:") return url;
|
|
13273
|
+
fresh.search = `${fresh.search === "" ? "?" : `${fresh.search}&`}load=${load}`;
|
|
13274
|
+
return fresh.href;
|
|
12450
13275
|
}
|
|
12451
13276
|
/**
|
|
12452
13277
|
* Create an empty {@link ReferenceTargets}.
|
|
@@ -12543,6 +13368,7 @@ function renderNode(node, key, context) {
|
|
|
12543
13368
|
title: mention.title,
|
|
12544
13369
|
"aria-label": mention.label,
|
|
12545
13370
|
onClick: mention.open,
|
|
13371
|
+
...mention.contextMenuProps,
|
|
12546
13372
|
children: [jsx(LinkIconMedium, {
|
|
12547
13373
|
kind: classifyLinkPath(value),
|
|
12548
13374
|
className: markdownCss.linkIcon
|
|
@@ -12692,11 +13518,15 @@ function renderSafeLink(href, children, key, glyph = true) {
|
|
|
12692
13518
|
}, key);
|
|
12693
13519
|
}
|
|
12694
13520
|
function MarkdownAnchor({ href, glyph, children }) {
|
|
12695
|
-
const { openExternalLink } = useMarkdownDelegate();
|
|
13521
|
+
const { openExternalLink, getTriggerProps } = useMarkdownDelegate();
|
|
12696
13522
|
const external = ["http:", "https:"].includes(new URL(href).protocol);
|
|
12697
13523
|
const open = external ? openExternalLink : void 0;
|
|
12698
13524
|
return jsxs("a", {
|
|
12699
13525
|
href,
|
|
13526
|
+
...external ? getTriggerProps?.({
|
|
13527
|
+
kind: "url",
|
|
13528
|
+
url: href
|
|
13529
|
+
}) : {},
|
|
12700
13530
|
...external ? {
|
|
12701
13531
|
target: "_blank",
|
|
12702
13532
|
rel: "noopener noreferrer"
|
|
@@ -12724,14 +13554,20 @@ function renderAnchor(url, children, key, glyph = true, streaming = false) {
|
|
|
12724
13554
|
return renderSafeLink(normalizeUri(url), children, key, glyph);
|
|
12725
13555
|
}
|
|
12726
13556
|
function MarkdownFileLink({ file, glyph, children }) {
|
|
12727
|
-
const { openFile, fileImages } = useMarkdownDelegate();
|
|
13557
|
+
const { openFile, fileImages, getTriggerProps } = useMarkdownDelegate();
|
|
13558
|
+
const [load] = useState(nextImageLoad);
|
|
12728
13559
|
if (openFile === void 0) return jsx(Fragment, { children });
|
|
12729
13560
|
const preview = glyph && classifyLinkPath(file.path) === "image" ? fileImages : void 0;
|
|
12730
|
-
const
|
|
13561
|
+
const resolved = preview?.resolve(file.path);
|
|
13562
|
+
const src = resolved === void 0 ? void 0 : freshImageUrl(resolved, load);
|
|
12731
13563
|
const anchor = jsxs("button", {
|
|
12732
13564
|
type: "button",
|
|
12733
13565
|
className: clsx(markdownCss.fileMention, markdownCss.fileLink),
|
|
12734
13566
|
title: src === void 0 ? file.path : void 0,
|
|
13567
|
+
...getTriggerProps?.({
|
|
13568
|
+
kind: "file",
|
|
13569
|
+
...file
|
|
13570
|
+
}),
|
|
12735
13571
|
onClick: () => {
|
|
12736
13572
|
openFile(file.path, file.line === void 0 ? void 0 : { line: file.line });
|
|
12737
13573
|
},
|
|
@@ -12779,8 +13615,10 @@ function renderImage(url, alt, key, context) {
|
|
|
12779
13615
|
}
|
|
12780
13616
|
function MarkdownImage({ destination, alt, pathImages, streaming, inLink }) {
|
|
12781
13617
|
const { fileImages } = useMarkdownDelegate();
|
|
13618
|
+
const [load] = useState(nextImageLoad);
|
|
12782
13619
|
const file = streaming ? void 0 : parseFileLink(destination);
|
|
12783
|
-
const
|
|
13620
|
+
const resolved = file === void 0 ? void 0 : fileImages?.resolve(file.path);
|
|
13621
|
+
const src = resolved === void 0 ? imageSource(destination, pathImages, load) : freshImageUrl(resolved, load);
|
|
12784
13622
|
if (src === void 0) return jsx("span", {
|
|
12785
13623
|
className: markdownCss.imageAlt,
|
|
12786
13624
|
children: alt
|
|
@@ -12793,6 +13631,7 @@ function MarkdownImage({ destination, alt, pathImages, streaming, inLink }) {
|
|
|
12793
13631
|
}, src);
|
|
12794
13632
|
}
|
|
12795
13633
|
function LoadedMarkdownImage({ src, alt, destination, preview }) {
|
|
13634
|
+
const { getTriggerProps } = useMarkdownDelegate();
|
|
12796
13635
|
const [failed, setFailed] = useState(false);
|
|
12797
13636
|
const [open, setOpen] = useState(false);
|
|
12798
13637
|
const close = useCallback(() => {
|
|
@@ -12802,6 +13641,12 @@ function LoadedMarkdownImage({ src, alt, destination, preview }) {
|
|
|
12802
13641
|
className: markdownCss.imageAlt,
|
|
12803
13642
|
children: [preview === void 0 ? "" : `${preview.labels.failed} · `, alt || destination]
|
|
12804
13643
|
});
|
|
13644
|
+
const file = parseFileLink(destination);
|
|
13645
|
+
const contextMenuProps = getTriggerProps?.({
|
|
13646
|
+
kind: "image",
|
|
13647
|
+
url: src,
|
|
13648
|
+
...file === void 0 ? {} : { path: file.path }
|
|
13649
|
+
});
|
|
12805
13650
|
const img = jsx("img", {
|
|
12806
13651
|
className: markdownCss.image,
|
|
12807
13652
|
src,
|
|
@@ -12811,13 +13656,15 @@ function LoadedMarkdownImage({ src, alt, destination, preview }) {
|
|
|
12811
13656
|
},
|
|
12812
13657
|
loading: "lazy",
|
|
12813
13658
|
decoding: "async",
|
|
12814
|
-
referrerPolicy: "no-referrer"
|
|
13659
|
+
referrerPolicy: "no-referrer",
|
|
13660
|
+
...contextMenuProps
|
|
12815
13661
|
});
|
|
12816
13662
|
if (preview === void 0) return img;
|
|
12817
13663
|
return jsxs(Fragment, { children: [jsx("button", {
|
|
12818
13664
|
type: "button",
|
|
12819
13665
|
className: markdownCss.imageButton,
|
|
12820
13666
|
title: preview.labels.open,
|
|
13667
|
+
...contextMenuProps,
|
|
12821
13668
|
"aria-label": alt ? `${preview.labels.open}: ${alt}` : preview.labels.open,
|
|
12822
13669
|
onClick: () => {
|
|
12823
13670
|
setOpen(true);
|
|
@@ -12827,7 +13674,8 @@ function LoadedMarkdownImage({ src, alt, destination, preview }) {
|
|
|
12827
13674
|
src,
|
|
12828
13675
|
alt,
|
|
12829
13676
|
labels: preview.labels,
|
|
12830
|
-
onClose: close
|
|
13677
|
+
onClose: close,
|
|
13678
|
+
contextMenuProps
|
|
12831
13679
|
})] });
|
|
12832
13680
|
}
|
|
12833
13681
|
/** The bracketed source text a reference reverts to when its definition is missing. */
|
|
@@ -13055,7 +13903,9 @@ var StreamingRenderer = class {
|
|
|
13055
13903
|
* text sizing, uniform bold headings, and tight block spacing; the default
|
|
13056
13904
|
* `body` variant uses the full document typography.
|
|
13057
13905
|
* The provider's `openFile` enables local Markdown links in settled messages,
|
|
13058
|
-
* including `#L24`
|
|
13906
|
+
* including `#L24` / `#L24-L30` fragments and `:24` / `:24-30` / `:24:5` suffixes
|
|
13907
|
+
* (ranges open at their first line, columns are ignored, and invalid locations
|
|
13908
|
+
* open only the file; see the README for URL/path classification).
|
|
13059
13909
|
* @returns A GFM document with TeX math rendered through KaTeX; raw HTML
|
|
13060
13910
|
* stays literal unless `html` supplies a renderer, and unsafe protocols are
|
|
13061
13911
|
* disabled. Local links without an opener remain text; absolute HTTP(S)
|
|
@@ -13150,7 +14000,7 @@ function SafeLink({ url, label, className }) {
|
|
|
13150
14000
|
children: [jsx(LinkIconMedium, {
|
|
13151
14001
|
kind: "url",
|
|
13152
14002
|
href,
|
|
13153
|
-
className: css$
|
|
14003
|
+
className: css$42.linkIcon
|
|
13154
14004
|
}), label]
|
|
13155
14005
|
});
|
|
13156
14006
|
}
|
|
@@ -13165,20 +14015,20 @@ function SafeLink({ url, label, className }) {
|
|
|
13165
14015
|
*/
|
|
13166
14016
|
function SourceItem({ source, ordinal }) {
|
|
13167
14017
|
return jsxs("li", {
|
|
13168
|
-
className: css$
|
|
14018
|
+
className: css$42.source,
|
|
13169
14019
|
value: ordinal,
|
|
13170
14020
|
children: [
|
|
13171
14021
|
jsx(SafeLink, {
|
|
13172
14022
|
url: source.url,
|
|
13173
14023
|
label: linkLabel(source.url, source.title),
|
|
13174
|
-
className: css$
|
|
14024
|
+
className: css$42.sourceLink
|
|
13175
14025
|
}),
|
|
13176
14026
|
source.snippet !== void 0 && source.snippet !== "" && jsx("div", {
|
|
13177
|
-
className: css$
|
|
14027
|
+
className: css$42.snippet,
|
|
13178
14028
|
children: source.snippet
|
|
13179
14029
|
}),
|
|
13180
14030
|
source.publishedAt !== void 0 && source.publishedAt !== "" && jsx("div", {
|
|
13181
|
-
className: css$
|
|
14031
|
+
className: css$42.published,
|
|
13182
14032
|
children: source.publishedAt
|
|
13183
14033
|
})
|
|
13184
14034
|
]
|
|
@@ -13193,28 +14043,28 @@ function SourceItem({ source, ordinal }) {
|
|
|
13193
14043
|
function WebSearchBlock({ answer, sources, truncated, labels, className }) {
|
|
13194
14044
|
const empty = (answer === void 0 || answer === "") && sources.length === 0;
|
|
13195
14045
|
return jsxs("div", {
|
|
13196
|
-
className: clsx(css$
|
|
14046
|
+
className: clsx(css$42.block, className),
|
|
13197
14047
|
"data-web": "search",
|
|
13198
14048
|
children: [
|
|
13199
14049
|
answer !== void 0 && answer !== "" && jsx("div", {
|
|
13200
|
-
className: css$
|
|
14050
|
+
className: css$42.answer,
|
|
13201
14051
|
children: jsx(MarkdownText, {
|
|
13202
14052
|
text: answer,
|
|
13203
14053
|
labels: labels.markdown
|
|
13204
14054
|
})
|
|
13205
14055
|
}),
|
|
13206
14056
|
empty ? jsx("div", {
|
|
13207
|
-
className: css$
|
|
14057
|
+
className: css$42.empty,
|
|
13208
14058
|
children: labels.noResults
|
|
13209
14059
|
}) : jsx("ol", {
|
|
13210
|
-
className: css$
|
|
14060
|
+
className: css$42.sources,
|
|
13211
14061
|
children: sources.map((source, index) => jsx(SourceItem, {
|
|
13212
14062
|
source,
|
|
13213
14063
|
ordinal: index + 1
|
|
13214
14064
|
}, index))
|
|
13215
14065
|
}),
|
|
13216
14066
|
truncated && jsx("div", {
|
|
13217
|
-
className: css$
|
|
14067
|
+
className: css$42.truncated,
|
|
13218
14068
|
children: labels.sourcesTruncated
|
|
13219
14069
|
})
|
|
13220
14070
|
]
|
|
@@ -13227,23 +14077,23 @@ function WebSearchBlock({ answer, sources, truncated, labels, className }) {
|
|
|
13227
14077
|
*/
|
|
13228
14078
|
function WebFetchBlock({ url, statusCode, truncated, labels, className }) {
|
|
13229
14079
|
return jsxs("div", {
|
|
13230
|
-
className: clsx(css$
|
|
14080
|
+
className: clsx(css$42.block, css$42.fetch, className),
|
|
13231
14081
|
"data-web": "fetch",
|
|
13232
14082
|
children: [jsx(SafeLink, {
|
|
13233
14083
|
url,
|
|
13234
14084
|
label: url,
|
|
13235
|
-
className: css$
|
|
14085
|
+
className: css$42.fetchUrl
|
|
13236
14086
|
}), jsxs("div", {
|
|
13237
|
-
className: css$
|
|
14087
|
+
className: css$42.fetchMeta,
|
|
13238
14088
|
children: [jsxs("span", {
|
|
13239
|
-
className: css$
|
|
14089
|
+
className: css$42.status,
|
|
13240
14090
|
children: [
|
|
13241
14091
|
labels.http,
|
|
13242
14092
|
" ",
|
|
13243
14093
|
statusCode
|
|
13244
14094
|
]
|
|
13245
14095
|
}), truncated && jsx("span", {
|
|
13246
|
-
className: css$
|
|
14096
|
+
className: css$42.truncated,
|
|
13247
14097
|
children: labels.contentTruncated
|
|
13248
14098
|
})]
|
|
13249
14099
|
})]
|
|
@@ -13258,6 +14108,124 @@ function WebBlock(props) {
|
|
|
13258
14108
|
return props.kind === "search" ? jsx(WebSearchBlock, { ...props }) : jsx(WebFetchBlock, { ...props });
|
|
13259
14109
|
}
|
|
13260
14110
|
//#endregion
|
|
14111
|
+
//#region lib/types/DetailDocument.js
|
|
14112
|
+
/**
|
|
14113
|
+
* The state {@link DetailDocument} shows for an owner's read: loading until it settles, then the document, none, or the
|
|
14114
|
+
* failure.
|
|
14115
|
+
* @param read - the owner's read; undefined before it starts.
|
|
14116
|
+
* @returns the state to show.
|
|
14117
|
+
*/
|
|
14118
|
+
function detailDocumentState(read) {
|
|
14119
|
+
if (read?.status === "ready") return read.markdown === void 0 ? { status: "none" } : {
|
|
14120
|
+
status: "found",
|
|
14121
|
+
markdown: read.markdown
|
|
14122
|
+
};
|
|
14123
|
+
return read?.status === "error" ? read : { status: "loading" };
|
|
14124
|
+
}
|
|
14125
|
+
/**
|
|
14126
|
+
* Assemble reference-stable labels for {@link DetailDocument} from translated strings.
|
|
14127
|
+
* @param copy - the translated strings.
|
|
14128
|
+
* @returns labels that keep their identity while the strings stay equal.
|
|
14129
|
+
*/
|
|
14130
|
+
function useDetailDocumentLabels(copy) {
|
|
14131
|
+
const { title, loading, retry, footnotes, copy: copyLabel, copied: copiedLabel, diagram, enlarged } = copy;
|
|
14132
|
+
const markdown = useMemo(() => ({
|
|
14133
|
+
code: {
|
|
14134
|
+
copyLabel,
|
|
14135
|
+
copiedLabel
|
|
14136
|
+
},
|
|
14137
|
+
mermaid: {
|
|
14138
|
+
diagramLabel: diagram,
|
|
14139
|
+
enlargedLabel: enlarged
|
|
14140
|
+
},
|
|
14141
|
+
footnotes
|
|
14142
|
+
}), [
|
|
14143
|
+
copyLabel,
|
|
14144
|
+
copiedLabel,
|
|
14145
|
+
diagram,
|
|
14146
|
+
enlarged,
|
|
14147
|
+
footnotes
|
|
14148
|
+
]);
|
|
14149
|
+
return useMemo(() => ({
|
|
14150
|
+
title,
|
|
14151
|
+
loading,
|
|
14152
|
+
retry,
|
|
14153
|
+
markdown
|
|
14154
|
+
}), [
|
|
14155
|
+
title,
|
|
14156
|
+
loading,
|
|
14157
|
+
retry,
|
|
14158
|
+
markdown
|
|
14159
|
+
]);
|
|
14160
|
+
}
|
|
14161
|
+
/** Authored raw HTML renders nothing in a detail document, which is written for HTML renderers such as package pages. */
|
|
14162
|
+
const OMIT_HTML = () => null;
|
|
14163
|
+
/**
|
|
14164
|
+
* A section showing a long Markdown description, without a heading of its own: three skeleton lines while it loads, the
|
|
14165
|
+
* document once found, or the owner's failure message with a retry. Nothing renders for `none`. A document taller than
|
|
14166
|
+
* the section's height limit scrolls inside it, and the scroll region, named by `labels.title`, takes keyboard focus.
|
|
14167
|
+
* Raw HTML is omitted, relative links stay text, and only images the owner's `images` resolver accepts, or absolute
|
|
14168
|
+
* HTTP(S) images, load.
|
|
14169
|
+
* @param props.state - the read to show.
|
|
14170
|
+
* @param props.labels - localized region name, placeholder name, retry text, and Markdown chrome; keep `markdown` reference-stable.
|
|
14171
|
+
* @param props.images - resolves the document's relative image destinations to displayable URLs.
|
|
14172
|
+
* @param props.onRetry - reads the document again after a failure.
|
|
14173
|
+
* @returns the section, or null.
|
|
14174
|
+
*/
|
|
14175
|
+
function DetailDocument({ state, labels, images, onRetry }) {
|
|
14176
|
+
if (state.status === "none") return null;
|
|
14177
|
+
let body;
|
|
14178
|
+
switch (state.status) {
|
|
14179
|
+
case "found":
|
|
14180
|
+
body = jsx("div", {
|
|
14181
|
+
className: css$43.document,
|
|
14182
|
+
role: "region",
|
|
14183
|
+
"aria-label": labels.title,
|
|
14184
|
+
tabIndex: 0,
|
|
14185
|
+
children: jsx(MarkdownText, {
|
|
14186
|
+
text: state.markdown,
|
|
14187
|
+
labels: labels.markdown,
|
|
14188
|
+
pathImages: images,
|
|
14189
|
+
html: OMIT_HTML
|
|
14190
|
+
})
|
|
14191
|
+
});
|
|
14192
|
+
break;
|
|
14193
|
+
case "error":
|
|
14194
|
+
body = jsxs("div", {
|
|
14195
|
+
className: css$43.failure,
|
|
14196
|
+
children: [jsxs("p", {
|
|
14197
|
+
className: css$43.error,
|
|
14198
|
+
role: "alert",
|
|
14199
|
+
children: [jsx(StateDot, { state: "error" }), state.message]
|
|
14200
|
+
}), jsx(Button, {
|
|
14201
|
+
variant: "outline",
|
|
14202
|
+
size: "sm",
|
|
14203
|
+
onClick: onRetry,
|
|
14204
|
+
children: labels.retry
|
|
14205
|
+
})]
|
|
14206
|
+
});
|
|
14207
|
+
break;
|
|
14208
|
+
case "loading":
|
|
14209
|
+
body = jsxs("div", {
|
|
14210
|
+
className: css$43.skeleton,
|
|
14211
|
+
role: "status",
|
|
14212
|
+
"aria-label": labels.loading,
|
|
14213
|
+
"aria-busy": "true",
|
|
14214
|
+
children: [
|
|
14215
|
+
jsx("span", { className: `${css$43.bar} ${css$43.long}` }),
|
|
14216
|
+
jsx("span", { className: `${css$43.bar} ${css$43.long}` }),
|
|
14217
|
+
jsx("span", { className: `${css$43.bar} ${css$43.short}` })
|
|
14218
|
+
]
|
|
14219
|
+
});
|
|
14220
|
+
break;
|
|
14221
|
+
}
|
|
14222
|
+
return jsx("section", {
|
|
14223
|
+
className: css$43.section,
|
|
14224
|
+
"data-detail-document": true,
|
|
14225
|
+
children: body
|
|
14226
|
+
});
|
|
14227
|
+
}
|
|
14228
|
+
//#endregion
|
|
13261
14229
|
//#region lib/types/markdown/JsonBlock.js
|
|
13262
14230
|
const MAX_CHARS = 2e4;
|
|
13263
14231
|
function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel }) {
|
|
@@ -13277,10 +14245,10 @@ function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel }) {
|
|
|
13277
14245
|
truncatedLabel
|
|
13278
14246
|
]);
|
|
13279
14247
|
return jsxs("div", {
|
|
13280
|
-
className: css$
|
|
14248
|
+
className: css$44.root,
|
|
13281
14249
|
children: [jsxs("button", {
|
|
13282
14250
|
type: "button",
|
|
13283
|
-
className: css$
|
|
14251
|
+
className: css$44.toggle,
|
|
13284
14252
|
onClick: () => {
|
|
13285
14253
|
setOpen((v) => !v);
|
|
13286
14254
|
},
|
|
@@ -13290,7 +14258,7 @@ function JsonBlock({ label, payload, defaultOpen = false, truncatedLabel }) {
|
|
|
13290
14258
|
label
|
|
13291
14259
|
]
|
|
13292
14260
|
}), open && jsx("pre", {
|
|
13293
|
-
className: css$
|
|
14261
|
+
className: css$44.body,
|
|
13294
14262
|
children: body
|
|
13295
14263
|
})]
|
|
13296
14264
|
});
|
|
@@ -13367,6 +14335,17 @@ function extractMarkdownPlainText(markdown, options = {}) {
|
|
|
13367
14335
|
}
|
|
13368
14336
|
}
|
|
13369
14337
|
//#endregion
|
|
14338
|
+
//#region lib/types/code-highlighting.js
|
|
14339
|
+
/** Shared filename-to-grammar selection and lazy line highlighter for source views. */
|
|
14340
|
+
/**
|
|
14341
|
+
* Bind the shared lazy highlighter to one language and refresh after its grammar loads.
|
|
14342
|
+
* @param language - grammar hint selected from the source filename.
|
|
14343
|
+
* @returns a stable fragment highlighter; unknown and loading grammars return `undefined` for plain-text fallback.
|
|
14344
|
+
*/
|
|
14345
|
+
function useCodeHighlighter(language) {
|
|
14346
|
+
return useCallback((code) => highlightLines(code, language), [language, useSyncExternalStore(subscribeGrammarLoaded, grammarLoadCount, grammarLoadCount)]);
|
|
14347
|
+
}
|
|
14348
|
+
//#endregion
|
|
13370
14349
|
//#region lib/types/MenuGroup.js
|
|
13371
14350
|
/** Accessible menu groups with shared sticky-heading presentation and viewport observation. */
|
|
13372
14351
|
/**
|
|
@@ -13380,17 +14359,17 @@ function MenuGroup({ label, children }) {
|
|
|
13380
14359
|
role: "group",
|
|
13381
14360
|
"aria-labelledby": headingId,
|
|
13382
14361
|
"data-menu-group": "",
|
|
13383
|
-
className: css$
|
|
14362
|
+
className: css$45.group,
|
|
13384
14363
|
children: [
|
|
13385
14364
|
jsx("span", {
|
|
13386
14365
|
"aria-hidden": "true",
|
|
13387
14366
|
"data-menu-group-start": "",
|
|
13388
|
-
className: css$
|
|
14367
|
+
className: css$45.start
|
|
13389
14368
|
}),
|
|
13390
14369
|
jsx("div", {
|
|
13391
14370
|
id: headingId,
|
|
13392
14371
|
"data-menu-group-heading": "",
|
|
13393
|
-
className: css$
|
|
14372
|
+
className: css$45.heading,
|
|
13394
14373
|
children: label
|
|
13395
14374
|
}),
|
|
13396
14375
|
children
|
|
@@ -13486,6 +14465,6 @@ function observeStickyMenuGroups(viewport) {
|
|
|
13486
14465
|
};
|
|
13487
14466
|
}
|
|
13488
14467
|
//#endregion
|
|
13489
|
-
export { Button, Checkbox, CodeBlock, CommandText, ConnectionIndicator, DEFAULT_DIFF_MAX_LINES, DEFAULT_READ_MAX_LINES, DEFAULT_SEARCH_MAX_LINES, DEFAULT_TERMINAL_MAX_LINES, DiffBlock, DisclosureRow, FileTypeIcon, HoverCard, ICON_MEDIUM_STROKE, ICON_REGULAR_STROKE, IconAgentPresetOutlineMedium, IconAgentPresetOutlineRegular, IconAlarmClockOutlineMedium, IconAlarmClockOutlineRegular, IconApiOutlineMedium, IconApiOutlineRegular, IconAppsAddOutlineMedium, IconAppsAddOutlineRegular, IconArchiveCheckOutlineMedium, IconArchiveCheckOutlineRegular, IconArchiveOffOutlineMedium, IconArchiveOffOutlineRegular, IconArchiveOutlineMedium, IconArchiveOutlineRegular, IconBranchOutlineMedium, IconBranchOutlineRegular, IconBrowseOutlineMedium, IconBrowseOutlineRegular, IconCheckCircleFillMedium, IconCheckCircleFillRegular, IconCheckCircleOutlineMedium, IconCheckCircleOutlineRegular, IconCheckOutlineMedium, IconCheckOutlineRegular, IconChecklistOutlineMedium, IconChecklistOutlineRegular, IconChevronDownOutlineMedium, IconChevronDownOutlineRegular, IconChevronLeftOutlineMedium, IconChevronLeftOutlineRegular, IconChevronRightOutlineMedium, IconChevronRightOutlineRegular, IconChevronUpOutlineMedium, IconChevronUpOutlineRegular, IconChevronsUpDownOutlineMedium, IconChevronsUpDownOutlineRegular, IconClockOutlineMedium, IconClockOutlineRegular, IconCloseCircleFillMedium, IconCloseCircleFillRegular, IconCloseFillMedium, IconCloseFillRegular, IconCloseOutlineMedium, IconCloseOutlineRegular, IconCodeOutlineMedium, IconCodeOutlineRegular, IconCompactOutlineMedium, IconCompactOutlineRegular, IconCompareSplitOutlineMedium, IconCompareSplitOutlineRegular, IconContextInjectionOutlineMedium, IconContextInjectionOutlineRegular, IconCopyOutlineMedium, IconCopyOutlineRegular, IconCordisPluginOutlineMedium, IconCordisPluginOutlineRegular, IconDarkOutlineMedium, IconDarkOutlineRegular, IconDataOutlineMedium, IconDataOutlineRegular, IconDatabaseOutlineMedium, IconDatabaseOutlineRegular, IconDeliverDocMedium, IconDeliverDocRegular, IconDislikeFillMedium, IconDislikeFillRegular, IconDislikeOutlineMedium, IconDislikeOutlineRegular, IconDownloadOutlineMedium, IconDownloadOutlineRegular, IconEditOutlineMedium, IconEditOutlineRegular, IconEllipsisOutlineMedium, IconEllipsisOutlineRegular, IconEnhanceOutlineMedium, IconEnhanceOutlineRegular, IconFlatListOutlineMedium, IconFlatListOutlineRegular, IconFolderCloseMedium, IconFolderCloseRegular, IconFolderOpenMedium, IconFolderOpenOutlineMedium, IconFolderOpenOutlineRegular, IconFolderOpenRegular, IconFollowsystemOutlineMedium, IconFollowsystemOutlineRegular, IconFullscreenOutlineMedium, IconFullscreenOutlineRegular, IconGaugeOutlineMedium, IconGaugeOutlineRegular, IconGlobeOutlineMedium, IconGlobeOutlineRegular, IconGoalOutlineMedium, IconGoalOutlineRegular, IconInfoOutlineMedium, IconInfoOutlineRegular, IconInspectOutlineMedium, IconInspectOutlineRegular, IconLightOutlineMedium, IconLightOutlineRegular, IconLikeFillMedium, IconLikeFillRegular, IconLikeOutlineMedium, IconLikeOutlineRegular, IconLinkOutlineMedium, IconLinkOutlineRegular, IconListPenOutlineMedium, IconListPenOutlineRegular, IconLoadingOutlineMedium, IconLoadingOutlineRegular, IconLogoutOutlineMedium, IconLogoutOutlineRegular, IconMicrophoneOutlineMedium, IconMicrophoneOutlineRegular, IconNewChatOutlineMedium, IconNewChatOutlineRegular, IconNowrapFillMedium, IconNowrapFillRegular, IconPanelLeftOutlineMedium, IconPanelLeftOutlineRegular, IconPaperPlaneOutlineMedium, IconPaperPlaneOutlineRegular, IconPaperclipOutlineMedium, IconPaperclipOutlineRegular, IconPauseOutlineMedium, IconPauseOutlineRegular, IconPersonalizationOutlineMedium, IconPersonalizationOutlineRegular, IconPinFillMedium, IconPinFillRegular, IconPinOutlineMedium, IconPinOutlineRegular, IconPlanOutlineMedium, IconPlanOutlineRegular, IconPlayOutlineMedium, IconPlayOutlineRegular, IconPluginPinwheelOutlineMedium, IconPluginPinwheelOutlineRegular, IconPlusOutlineMedium, IconPlusOutlineRegular, IconProjectAddOutlineMedium, IconProjectAddOutlineRegular, IconQuestionOutlineMedium, IconQuestionOutlineRegular, IconQueueOutlineMedium, IconQueueOutlineRegular, IconRefreshOutlineMedium, IconRefreshOutlineRegular, IconRightUpOutlineMedium, IconRightUpOutlineRegular, IconSearchOutlineMedium, IconSearchOutlineRegular, IconSendOutlineMedium, IconSendOutlineRegular, IconSettingsOutlineMedium, IconSettingsOutlineRegular, IconShareOutlineMedium, IconShareOutlineRegular, IconShieldOutlineMedium, IconShieldOutlineRegular, IconSkillOutlineMedium, IconSkillOutlineRegular, IconSlidersTwoOutlineMedium, IconSlidersTwoOutlineRegular, IconSparkleMedium, IconSparkleRegular, IconStopFillMedium, IconStopFillRegular, IconTerminalOutlineMedium, IconTerminalOutlineRegular, IconThinkOutlineMedium, IconThinkOutlineRegular, IconTrashOutlineMedium, IconTrashOutlineRegular, IconTreeCornerMedium, IconTreeCornerRegular, IconTriangleRightFillMedium, IconTriangleRightFillRegular, IconUnarchiveOutlineMedium, IconUnarchiveOutlineRegular, IconUserOutlineMedium, IconUserOutlineRegular, IconUsersOutlineMedium, IconUsersOutlineRegular, IconWarningOutlineMedium, IconWarningOutlineRegular, IconWarningTriangleOutlineMedium, IconWarningTriangleOutlineRegular, IconWorkspaceTreeOutlineMedium, IconWorkspaceTreeOutlineRegular, IconWrapFillMedium, IconWrapFillRegular, IconWrapLinesOutlineMedium, IconWrapLinesOutlineRegular, ImageLightbox, InlineEditor, Input, JsonBlock, JsonTree, LinkIconMedium, LinkIconRegular, MarkdownDelegateProvider, MarkdownText, Menu, MenuGroup, MenuItemButton, MenuSurface, Modal, OnboardingSurface, PathLabel, PermissionIconAutoReviewMedium, PermissionIconAutoReviewRegular, PermissionIconFullAccessMedium, PermissionIconFullAccessRegular, PermissionIconReadOnlyMedium, PermissionIconReadOnlyRegular, PermissionIconWorkspaceWriteMedium, PermissionIconWorkspaceWriteRegular, Pill, PluginArtworkDefault, PluginArtworkLoop, PluginArtworkSearch, PluginArtworkSubagent, PluginArtworkTerminal, QilinSeal, ReadBlock, ReferenceIconMedium, ReferenceIconRegular, RiskConfirmation, SHIELD_OUTLINE_PATH, SearchBlock, SegmentedControl, SegmentedTabs, SettingsForm, SettingsFormModel, SettingsSecretField, SettingsValueField, ShortcutKeys, StateDot, Switch, Tag, TerminalBlock, TextShimmer, Toast, Tooltip, WebBlock, classifyFileType, classifyLinkPath, closeTopModal, diffTotals, extractMarkdownPlainText, fileExtension, fileSizeRoundedText, fileSizeText, focusWithoutRing, isBehindModal, isDarwinDesktop, modalSelector, observeComposition, observeStickyMenuGroups, overlayTopMargin, pointerModality, projectUserText, rankByName, relativeTime, settingsNumberField, settingsTextField, useAnchoredMaxHeight, useAnchoredPosition,
|
|
14468
|
+
export { AnimatedRows, Button, CODE_HIGHLIGHT_EXTENSIONS, Checkbox, CodeBlock, CommandText, ConnectionIndicator, DEFAULT_DIFF_MAX_LINES, DEFAULT_READ_MAX_LINES, DEFAULT_SEARCH_MAX_LINES, DEFAULT_TERMINAL_MAX_LINES, DetailDocument, DiffBlock, DisclosureRow, FileTypeIcon, HoverCard, ICON_MEDIUM_STROKE, ICON_REGULAR_STROKE, IconAgentPresetOutlineMedium, IconAgentPresetOutlineRegular, IconAlarmClockOutlineMedium, IconAlarmClockOutlineRegular, IconApiOutlineMedium, IconApiOutlineRegular, IconAppsAddOutlineMedium, IconAppsAddOutlineRegular, IconArchiveCheckOutlineMedium, IconArchiveCheckOutlineRegular, IconArchiveOffOutlineMedium, IconArchiveOffOutlineRegular, IconArchiveOutlineMedium, IconArchiveOutlineRegular, IconBranchOutlineMedium, IconBranchOutlineRegular, IconBrowseOutlineMedium, IconBrowseOutlineRegular, IconCheckCircleFillMedium, IconCheckCircleFillRegular, IconCheckCircleOutlineMedium, IconCheckCircleOutlineRegular, IconCheckOutlineMedium, IconCheckOutlineRegular, IconChecklistOutlineMedium, IconChecklistOutlineRegular, IconChevronDownOutlineMedium, IconChevronDownOutlineRegular, IconChevronLeftOutlineMedium, IconChevronLeftOutlineRegular, IconChevronRightOutlineMedium, IconChevronRightOutlineRegular, IconChevronUpOutlineMedium, IconChevronUpOutlineRegular, IconChevronsUpDownOutlineMedium, IconChevronsUpDownOutlineRegular, IconClockOutlineMedium, IconClockOutlineRegular, IconCloseCircleFillMedium, IconCloseCircleFillRegular, IconCloseFillMedium, IconCloseFillRegular, IconCloseOutlineMedium, IconCloseOutlineRegular, IconCodeOutlineMedium, IconCodeOutlineRegular, IconCompactOutlineMedium, IconCompactOutlineRegular, IconCompareSplitOutlineMedium, IconCompareSplitOutlineRegular, IconContextInjectionOutlineMedium, IconContextInjectionOutlineRegular, IconCopyOutlineMedium, IconCopyOutlineRegular, IconCordisPluginOutlineMedium, IconCordisPluginOutlineRegular, IconDarkOutlineMedium, IconDarkOutlineRegular, IconDataOutlineMedium, IconDataOutlineRegular, IconDatabaseOutlineMedium, IconDatabaseOutlineRegular, IconDeliverDocMedium, IconDeliverDocRegular, IconDislikeFillMedium, IconDislikeFillRegular, IconDislikeOutlineMedium, IconDislikeOutlineRegular, IconDownloadOutlineMedium, IconDownloadOutlineRegular, IconDragHandleOutlineMedium, IconDragHandleOutlineRegular, IconEditOutlineMedium, IconEditOutlineRegular, IconEllipsisOutlineMedium, IconEllipsisOutlineRegular, IconEnhanceOutlineMedium, IconEnhanceOutlineRegular, IconFlatListOutlineMedium, IconFlatListOutlineRegular, IconFolderCloseMedium, IconFolderCloseRegular, IconFolderOpenMedium, IconFolderOpenOutlineMedium, IconFolderOpenOutlineRegular, IconFolderOpenRegular, IconFollowsystemOutlineMedium, IconFollowsystemOutlineRegular, IconFullscreenOutlineMedium, IconFullscreenOutlineRegular, IconGaugeOutlineMedium, IconGaugeOutlineRegular, IconGlobeOutlineMedium, IconGlobeOutlineRegular, IconGoalOutlineMedium, IconGoalOutlineRegular, IconInfoOutlineMedium, IconInfoOutlineRegular, IconInspectOutlineMedium, IconInspectOutlineRegular, IconLightOutlineMedium, IconLightOutlineRegular, IconLikeFillMedium, IconLikeFillRegular, IconLikeOutlineMedium, IconLikeOutlineRegular, IconLinkOutlineMedium, IconLinkOutlineRegular, IconListPenOutlineMedium, IconListPenOutlineRegular, IconLoadingOutlineMedium, IconLoadingOutlineRegular, IconLogoutOutlineMedium, IconLogoutOutlineRegular, IconMicrophoneOutlineMedium, IconMicrophoneOutlineRegular, IconNewChatOutlineMedium, IconNewChatOutlineRegular, IconNowrapFillMedium, IconNowrapFillRegular, IconPanelLeftOutlineMedium, IconPanelLeftOutlineRegular, IconPaperPlaneOutlineMedium, IconPaperPlaneOutlineRegular, IconPaperclipOutlineMedium, IconPaperclipOutlineRegular, IconPauseOutlineMedium, IconPauseOutlineRegular, IconPersonalizationOutlineMedium, IconPersonalizationOutlineRegular, IconPinFillMedium, IconPinFillRegular, IconPinOutlineMedium, IconPinOutlineRegular, IconPlanOutlineMedium, IconPlanOutlineRegular, IconPlayOutlineMedium, IconPlayOutlineRegular, IconPluginPinwheelOutlineMedium, IconPluginPinwheelOutlineRegular, IconPlusOutlineMedium, IconPlusOutlineRegular, IconProjectAddOutlineMedium, IconProjectAddOutlineRegular, IconQuestionOutlineMedium, IconQuestionOutlineRegular, IconQueueOutlineMedium, IconQueueOutlineRegular, IconRefreshOutlineMedium, IconRefreshOutlineRegular, IconRightUpOutlineMedium, IconRightUpOutlineRegular, IconSaveOutlineMedium, IconSaveOutlineRegular, IconSearchOutlineMedium, IconSearchOutlineRegular, IconSendOutlineMedium, IconSendOutlineRegular, IconSettingsOutlineMedium, IconSettingsOutlineRegular, IconShareOutlineMedium, IconShareOutlineRegular, IconShieldOutlineMedium, IconShieldOutlineRegular, IconSkillOutlineMedium, IconSkillOutlineRegular, IconSlidersTwoOutlineMedium, IconSlidersTwoOutlineRegular, IconSparkleMedium, IconSparkleRegular, IconStopFillMedium, IconStopFillRegular, IconTerminalOutlineMedium, IconTerminalOutlineRegular, IconThinkOutlineMedium, IconThinkOutlineRegular, IconTrashOutlineMedium, IconTrashOutlineRegular, IconTreeCornerMedium, IconTreeCornerRegular, IconTriangleRightFillMedium, IconTriangleRightFillRegular, IconUnarchiveOutlineMedium, IconUnarchiveOutlineRegular, IconUserOutlineMedium, IconUserOutlineRegular, IconUsersOutlineMedium, IconUsersOutlineRegular, IconWarningOutlineMedium, IconWarningOutlineRegular, IconWarningTriangleOutlineMedium, IconWarningTriangleOutlineRegular, IconWorkspaceTreeOutlineMedium, IconWorkspaceTreeOutlineRegular, IconWrapFillMedium, IconWrapFillRegular, IconWrapLinesOutlineMedium, IconWrapLinesOutlineRegular, ImageLightbox, InlineEditor, Input, JsonBlock, JsonTree, LinkIconMedium, LinkIconRegular, MENU_TRANSITION_MS, MarkdownDelegateProvider, MarkdownText, Menu, MenuGroup, MenuItemButton, MenuSurface, Modal, OnboardingSurface, PathLabel, PermissionIconAutoReviewMedium, PermissionIconAutoReviewRegular, PermissionIconFullAccessMedium, PermissionIconFullAccessRegular, PermissionIconReadOnlyMedium, PermissionIconReadOnlyRegular, PermissionIconWorkspaceWriteMedium, PermissionIconWorkspaceWriteRegular, Pill, PluginArtworkDefault, PluginArtworkLoop, PluginArtworkSearch, PluginArtworkSubagent, PluginArtworkTerminal, QUOTE_WIRE_SCHEME, QilinSeal, QuotePreview, ReadBlock, ReferenceIconMedium, ReferenceIconRegular, RiskConfirmation, SHIELD_OUTLINE_PATH, SearchBlock, SegmentedControl, SegmentedTabs, SettingsForm, SettingsFormModel, SettingsSecretField, SettingsValueField, ShortcutKeys, StateDot, Switch, Tag, TerminalBlock, TextShimmer, Toast, Tooltip, UserMessageBubble, WebBlock, classifyFileType, classifyLinkPath, closeTopModal, detailDocumentState, diffTotals, extractMarkdownPlainText, fileExtension, fileSizeRoundedText, fileSizeText, focusWithoutRing, isBehindModal, isDarwinDesktop, isMobileDevice, languageForPath, messageQuoteRanges, modalSelector, observeComposition, observeStickyMenuGroups, overlayTopMargin, pointerModality, prefersReducedMotion, projectUserText, quoteBlockTextOf, quoteInsertionOf, quoteTextOf, quotedExcerptOf, rankByName, relativeTime, settingsNumberField, settingsTextField, useAnchoredMaxHeight, useAnchoredPosition, useClipboardCopy, useCodeHighlighter, useDetailDocumentLabels, useDismissOnOutsidePointer, useModalLayer, useRowDragPreview, watchDarwinDesktop, writeClipboard };
|
|
13490
14469
|
|
|
13491
14470
|
//# sourceMappingURL=index.js.map
|