@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.
Files changed (55) hide show
  1. package/README.i18n.yaml +8 -8
  2. package/README.md +11 -4
  3. package/README.zh.md +12 -4
  4. package/lib/AnimatedRows.module.css +7 -0
  5. package/lib/Button.module.css +6 -3
  6. package/lib/DetailDocument.module.css +67 -0
  7. package/lib/HoverCard.module.css +3 -3
  8. package/lib/ImageLightbox.module.css +1 -1
  9. package/lib/InlineEditor.module.css +1 -1
  10. package/lib/Input.module.css +2 -2
  11. package/lib/Menu.module.css +127 -78
  12. package/lib/MenuSurface.module.css +23 -7
  13. package/lib/QuotePreview.module.css +89 -0
  14. package/lib/RowDragPreview.module.css +12 -0
  15. package/lib/SegmentedTabs.module.css +4 -1
  16. package/lib/Tag.module.css +1 -1
  17. package/lib/TerminalBlock.module.css +1 -1
  18. package/lib/Toast.module.css +28 -0
  19. package/lib/UserMessageBubble.module.css +11 -0
  20. package/lib/index.js +2094 -1115
  21. package/lib/markdown/CodeBlock.module.css +23 -0
  22. package/lib/markdown/JsonBlock.module.css +1 -1
  23. package/lib/markdown/MarkdownText.module.css +11 -7
  24. package/lib/settings-form/fields.module.css +2 -2
  25. package/lib/types/AnimatedRows.d.ts +46 -0
  26. package/lib/types/Button.d.ts +13 -4
  27. package/lib/types/DetailDocument.d.ts +79 -0
  28. package/lib/types/ImageLightbox.d.ts +3 -1
  29. package/lib/types/Menu.d.ts +25 -9
  30. package/lib/types/MenuSurface.d.ts +9 -0
  31. package/lib/types/QuotePreview.d.ts +26 -0
  32. package/lib/types/SegmentedTabs.d.ts +3 -1
  33. package/lib/types/Toast.d.ts +19 -1
  34. package/lib/types/Tooltip.d.ts +13 -8
  35. package/lib/types/UserMessageBubble.d.ts +11 -0
  36. package/lib/types/code-highlighting.d.ts +12 -0
  37. package/lib/types/focus.d.ts +8 -0
  38. package/lib/types/icons/index.d.ts +8 -0
  39. package/lib/types/icons/shared-artwork.d.ts +1 -0
  40. package/lib/types/index.d.ts +20 -4
  41. package/lib/types/markdown/MarkdownDelegate.d.ts +19 -2
  42. package/lib/types/markdown/MarkdownText.d.ts +3 -1
  43. package/lib/types/markdown/file-link.d.ts +13 -3
  44. package/lib/types/markdown/mathCompatibility.d.ts +12 -5
  45. package/lib/types/markdown/render.d.ts +2 -1
  46. package/lib/types/markdown/windows-path-destinations.d.ts +12 -0
  47. package/lib/types/motion.d.ts +13 -0
  48. package/lib/types/overlay-top-margin.d.ts +10 -2
  49. package/lib/types/quote-text.d.ts +69 -0
  50. package/lib/types/use-clipboard-copy.d.ts +14 -0
  51. package/lib/types/useAnchoredPosition.d.ts +31 -18
  52. package/lib/types/useRowDragPreview.d.ts +20 -0
  53. package/lib/types/user-text.d.ts +24 -2
  54. package/package.json +8 -6
  55. 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 "./Button.module.css";
13
- import css$8 from "./Pill.module.css";
14
- import css$9 from "./SegmentedControl.module.css";
15
- import css$10 from "./SegmentedTabs.module.css";
16
- import css$11 from "./Switch.module.css";
17
- import css$12 from "./Checkbox.module.css";
18
- import css$13 from "./Input.module.css";
19
- import css$14 from "./InlineEditor.module.css";
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$15 from "./ShortcutKeys.module.css";
22
- import css$16 from "./MenuSurface.module.css";
23
- import css$17 from "./Menu.module.css";
24
- import css$18 from "./Tooltip.module.css";
25
- import css$19 from "./HoverCard.module.css";
26
- import css$20 from "./Modal.module.css";
27
- import css$21 from "./OnboardingSurface.module.css";
28
- import css$22 from "./RiskConfirmation.module.css";
29
- import css$23 from "./ConnectionIndicator.module.css";
30
- import css$24 from "./FileTypeIcon.module.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$25 from "./PathLabel.module.css";
34
- import css$26 from "./SecretText.module.css";
35
- import css$27 from "./user-text.module.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$28 from "./Toast.module.css";
38
- import css$29 from "./JsonTree.module.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$30 from "./TerminalBlock.module.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$31 from "./ReadBlock.module.css";
50
+ import css$35 from "./ReadBlock.module.css";
47
51
  import { structuredPatch } from "diff";
48
- import css$32 from "./DiffBlock.module.css";
49
- import css$33 from "./SearchBlock.module.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$34 from "./markdown/CodeBlock.module.css";
62
- import css$35 from "./markdown/mermaid.module.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$36 from "./ImageLightbox.module.css";
65
- import css$37 from "./ImagePreview.module.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$38 from "./WebBlock.module.css";
68
- import css$39 from "./markdown/JsonBlock.module.css";
69
- import css$40 from "./MenuGroup.module.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$7.button, css$7[variant], css$7[size], className),
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$7.icon,
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$8.pill, active && css$8.active, className),
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$8.pill, css$8.interactive, active && css$8.active, className),
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$9.control, className),
4303
+ className: clsx(css$11.control, className),
3986
4304
  style: indicator,
3987
4305
  children: [jsx("span", {
3988
4306
  "aria-hidden": "true",
3989
- className: css$9.indicator
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$9.tab,
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$10.tabs, className),
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$10.indicator,
4377
+ className: css$12.indicator,
4058
4378
  "aria-hidden": "true",
4059
4379
  style: {
4060
- width: `calc((100% - 8px) / ${items.length})`,
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$10.tab,
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$11.switch, className),
4620
+ className: clsx(css$13.switch, className),
4301
4621
  onClick: () => {
4302
4622
  onChange(!checked);
4303
4623
  },
4304
- children: jsx("span", { className: css$11.thumb })
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$12.checkbox, className),
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$13.wrap, className),
4664
+ className: clsx(css$15.wrap, className),
4345
4665
  children: [icon != null && jsx("span", {
4346
- className: css$13.icon,
4666
+ className: css$15.icon,
4347
4667
  children: icon
4348
4668
  }), jsx("input", {
4349
4669
  ref,
4350
- className: css$13.input,
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$14.editor, className),
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
- * @returns the larger of `min` and the frame clearance plus 20px; fullscreen keeps only the 20px gap.
4416
- */
4417
- function overlayTopMargin(min) {
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
- return Math.max(min, (root.hasAttribute("data-fullscreen") ? 0 : clearance) + 20);
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$15.keys, variant === "tooltip" && css$15.tooltip, variant === "tooltip" && keys.includes("+") && css$15.joined, className),
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$15.separator : css$15.key,
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 = { "--qilin-menu-anchor": `--qilin-menu-${id.replaceAll(":", "")}` };
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
- className: clsx(css$16.surface, compact && css$16.compact, className),
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$16.material
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
- className: clsx(css$16.backing, compact && css$16.compact),
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/Menu.js
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
- * Render one `role="menuitem"` row for a {@link Menu} whose rows are
4684
- * components rather than `items` data: the same markup and styling as a data
4685
- * row, so it joins the list's keyboard walk and post-selection focus return
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 MenuItemButton({ children, shortcut, icon, current, disabled = false, danger = false, separatorBefore = false, onSelect }) {
4699
- return jsxs("div", {
4700
- className: css$17.itemWrap,
4701
- children: [separatorBefore && jsx("div", {
4702
- className: css$17.separator,
4703
- role: "separator"
4704
- }), jsxs("button", {
4705
- type: "button",
4706
- role: "menuitem",
4707
- className: clsx(css$17.item, danger && css$17.danger),
4708
- disabled,
4709
- "aria-keyshortcuts": shortcut?.aria,
4710
- "aria-current": current === true ? "page" : void 0,
4711
- onClick: onSelect,
4712
- children: [
4713
- icon !== void 0 && jsx("span", {
4714
- className: css$17.itemIcon,
4715
- children: icon
4716
- }),
4717
- jsx("span", {
4718
- className: css$17.itemLabel,
4719
- children
4720
- }),
4721
- shortcut !== void 0 && jsx("span", {
4722
- "aria-hidden": "true",
4723
- className: css$17.shortcut,
4724
- children: jsx(ShortcutKeys, {
4725
- keys: shortcut.keys,
4726
- className: css$17.shortcutKeys
4727
- })
4728
- })
4729
- ]
4730
- })]
4731
- });
4732
- }
4733
- function isSeparator(entry) {
4734
- return "type" in entry && entry.type === "separator";
4735
- }
4736
- function isLabel(entry) {
4737
- return "type" in entry && entry.type === "label";
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
- /** Unplaced portal list: hidden but laid out at a fixed origin so offsetWidth/offsetHeight are real. */
4740
- const MEASURE_STYLE = {
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. When no side fits, the
5490
- * viewport-bounded fallback may overlap the anchor; oversized content is scrollable when pinned.
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
- const current = anchor.current ?? eventAnchor.current;
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 ?? eventAnchor.current;
5599
- /* v8 ignore next -- show() only runs from a raising event, which recorded its anchor synchronously. */
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
- dismiss();
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
- document.addEventListener("keydown", onKeyDown, true);
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
- document.removeEventListener("keydown", onKeyDown, true);
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
- dismiss,
5649
- openOnClick,
5650
- visible
5952
+ open,
5953
+ onClose,
5954
+ autoFocus,
5955
+ contextual,
5956
+ exiting
5651
5957
  ]);
5652
- const content = visible && !suppressed && jsxs("span", {
5653
- ref: bubble,
5654
- id: openOnClick ? id : void 0,
5655
- className: css$18.bubble,
5656
- "data-side": side,
5657
- "data-portal": portal || void 0,
5658
- "data-pinned": pinned || void 0,
5659
- "data-align": align,
5660
- "data-has-shortcut": shortcutKeys?.length ? true : void 0,
5661
- style: {
5662
- left: pos.left,
5663
- top: pos.top,
5664
- visibility: "hidden",
5665
- ...maxWidth === void 0 ? {} : { maxWidth: `min(${maxWidth}px, calc(100vw - 24px))` }
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
- role: "tooltip",
5668
- "aria-label": shortcutKeys?.length ? [resolvedLabel, shortcutKeys.join(" ")].filter(Boolean).join(" ") : void 0,
5669
- children: [resolvedLabel && jsx("span", {
5670
- className: css$18.label,
5671
- children: resolvedLabel
5672
- }), shortcutKeys !== void 0 && shortcutKeys.length > 0 && jsx(ShortcutKeys, {
5673
- keys: shortcutKeys,
5674
- variant: "tooltip"
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(TooltipSuppression.Provider, {
5678
- value: setSuppressed,
5679
- children: [cloneElement(children, {
5680
- ref: mergedRef,
5681
- "aria-describedby": openOnClick && visible ? [children.props["aria-describedby"], id].filter(Boolean).join(" ") : children.props["aria-describedby"],
5682
- onMouseEnter: (e) => {
5683
- children.props.onMouseEnter?.(e);
5684
- trackAnchor(e);
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$19.card, inline && css$19.media, copyable && css$19.copyable, copied && css$19.feedback),
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$19.copied,
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$19.root, inline && css$19.inline),
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$19.status,
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$20.root,
6460
+ className: css$22.root,
5989
6461
  role: "presentation",
5990
6462
  onKeyDownCapture,
5991
6463
  children: [jsx("div", {
5992
- className: css$20.mask,
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$20.dialog, className),
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$20.content, contentClassName),
6477
+ className: clsx(css$22.content, contentClassName),
6006
6478
  children: [
6007
6479
  jsxs("div", {
6008
- className: css$20.header,
6480
+ className: css$22.header,
6009
6481
  children: [jsx("h2", {
6010
- className: css$20.title,
6482
+ className: css$22.title,
6011
6483
  children: title
6012
6484
  }), jsx("button", {
6013
6485
  type: "button",
6014
- className: css$20.close,
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$20.description,
6493
+ className: css$22.description,
6022
6494
  children: description
6023
6495
  }),
6024
6496
  children !== void 0 && jsx("div", {
6025
- className: css$20.body,
6497
+ className: css$22.body,
6026
6498
  children
6027
6499
  })
6028
6500
  ]
6029
6501
  }), footer !== void 0 && jsx("div", {
6030
- className: css$20.footer,
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$21.onboardingOverlay,
6526
+ className: css$23.onboardingOverlay,
6055
6527
  role: "presentation",
6056
6528
  children: [jsx("div", {
6057
- className: css$21.onboardingMask,
6529
+ className: css$23.onboardingMask,
6058
6530
  "aria-hidden": "true"
6059
6531
  }), jsx("div", {
6060
- className: css$21.onboardingStage,
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$22.confirmation ?? "",
6082
- contentClassName: css$22.confirmationContent ?? "",
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$22.modalAction,
6557
+ className: css$24.modalAction,
6086
6558
  onClick: onCancel,
6087
6559
  children: cancelLabel
6088
6560
  }), jsx(Button, {
6089
6561
  variant: "primary",
6090
- className: css$22.confirmAction,
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$22.warning,
6568
+ className: css$24.warning,
6097
6569
  children: [jsx(IconWarningOutlineRegular, {
6098
6570
  size: 18,
6099
- className: css$22.warningIcon
6571
+ className: css$24.warningIcon
6100
6572
  }), jsx("p", { children: description })]
6101
6573
  }), jsxs("label", {
6102
- className: css$22.acknowledgement,
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$23.leaving}` : "";
6622
+ const leavingClass = leaving ? ` ${css$25.leaving}` : "";
6151
6623
  if (rendered === "recovered") return jsxs("div", {
6152
- className: `${css$23.indicator} ${css$23.success}${leavingClass}`,
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$23.icon,
6628
+ className: css$25.icon,
6157
6629
  "aria-hidden": "true",
6158
6630
  children: jsx(IconCheckOutlineRegular, { size: 14 })
6159
6631
  }), jsx("span", {
6160
- className: css$23.label,
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$23.indicator} ${css$23.warning}${leavingClass}`,
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$23.icon,
6644
+ className: css$25.icon,
6173
6645
  "aria-hidden": "true",
6174
6646
  children: connecting ? jsx(IconLoadingOutlineRegular, {
6175
6647
  size: 14,
6176
- className: css$23.spinner
6648
+ className: css$25.spinner
6177
6649
  }) : jsx(IconRefreshOutlineRegular, { size: 14 })
6178
6650
  }), jsx("span", {
6179
- className: css$23.label,
6651
+ className: css$25.label,
6180
6652
  children: connecting ? jsxs(Fragment, { children: [connectingLabel, jsxs("span", {
6181
- className: css$23.dots,
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$23.secondDot,
6658
+ className: css$25.secondDot,
6187
6659
  children: "."
6188
6660
  }),
6189
6661
  jsx("span", {
6190
- className: css$23.thirdDot,
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$24.icon, css$24[resolvedType], className));
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$25.path, className),
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$25.text,
8024
+ className: css$27.text,
7553
8025
  children: [directory !== "" && jsx("span", {
7554
- className: css$25.directory,
8026
+ className: css$27.directory,
7555
8027
  children: directory
7556
8028
  }), jsx("span", {
7557
- className: css$25.name,
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$26.icon,
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$26.mask,
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$26.secret,
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$26.toggle,
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$27.plainRun,
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$27.refIcon
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$27.refChip, referenceKind === void 0 && css$27.slashChip);
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$27.plainRun,
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(onDone, holdMs + FADE_MS);
9219
+ const timer = setTimeout(() => {
9220
+ latestOnDone.current();
9221
+ }, holdMs + FADE_MS);
8478
9222
  return () => {
8479
9223
  clearTimeout(timer);
8480
9224
  };
8481
- }, [holdMs, onDone]);
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$28.toast,
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$28.icon} ${css$28.success}`,
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$28.icon,
9251
+ className: css$31.icon,
8508
9252
  "aria-hidden": true,
8509
9253
  children: icon
8510
- }), jsx("span", {
8511
- className: css$28.text,
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/use-copy-feedback.js
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
- * Copy `text` to the clipboard with one-second success feedback.
8552
- * @param text - the text to write on copy.
8553
- * @returns the `copied` flag and the `onCopy` handler.
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 useCopyFeedback(text) {
8556
- const [copied, setCopied] = useState(false);
8557
- return {
8558
- copied,
8559
- onCopy: useCallback(() => {
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$29.copySlot,
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$29.actionButton,
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$29.keywordValue,
9590
+ className: css$33.keywordValue,
8854
9591
  children: "null"
8855
9592
  });
8856
9593
  if (typeof value === "string") return jsx("span", {
8857
- className: css$29.stringValue,
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$29.numberValue,
9598
+ className: css$33.numberValue,
8862
9599
  children: String(value)
8863
9600
  });
8864
9601
  if (typeof value === "boolean") return jsx("span", {
8865
- className: css$29.keywordValue,
9602
+ className: css$33.keywordValue,
8866
9603
  children: String(value)
8867
9604
  });
8868
9605
  if (typeof value === "bigint") return jsx("span", {
8869
- className: css$29.otherValue,
9606
+ className: css$33.otherValue,
8870
9607
  children: value.toString()
8871
9608
  });
8872
9609
  if (typeof value === "undefined") return jsx("span", {
8873
- className: css$29.otherValue,
9610
+ className: css$33.otherValue,
8874
9611
  children: "undefined"
8875
9612
  });
8876
9613
  if (typeof value === "symbol") return jsx("span", {
8877
- className: css$29.otherValue,
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$29.otherValue,
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$29.punctuation,
9632
+ className: css$33.punctuation,
8896
9633
  children: open
8897
9634
  }),
8898
9635
  depth >= PREVIEW_DEPTH_LIMIT ? jsx("span", {
8899
- className: css$29.previewEllipsis,
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$29.punctuation,
9640
+ className: css$33.punctuation,
8904
9641
  children: ", "
8905
9642
  }),
8906
9643
  !array && jsxs(Fragment, { children: [jsx("span", {
8907
- className: css$29.previewProperty,
9644
+ className: css$33.previewProperty,
8908
9645
  children: key
8909
9646
  }), jsx("span", {
8910
- className: css$29.punctuation,
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$29.previewEllipsis,
9653
+ className: css$33.previewEllipsis,
8917
9654
  children: ", …"
8918
9655
  }),
8919
9656
  jsx("span", {
8920
- className: css$29.punctuation,
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$29.keywordValue,
9664
+ className: css$33.keywordValue,
8928
9665
  children: "null"
8929
9666
  });
8930
9667
  if (typeof value === "string") return jsx("span", {
8931
- className: css$29.stringValue,
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$29.keywordValue,
9672
+ className: css$33.keywordValue,
8936
9673
  children: String(value)
8937
9674
  });
8938
9675
  if (typeof value === "number") return jsx("span", {
8939
- className: css$29.numberValue,
9676
+ className: css$33.numberValue,
8940
9677
  children: String(value)
8941
9678
  });
8942
9679
  if (typeof value === "bigint") return jsx("span", {
8943
- className: css$29.numberValue,
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$29.otherValue,
9684
+ className: css$33.otherValue,
8948
9685
  children: value.toISOString()
8949
9686
  });
8950
9687
  if (typeof value === "function") return jsxs("span", {
8951
- className: css$29.otherValue,
9688
+ className: css$33.otherValue,
8952
9689
  children: ["function() ", "{ }"]
8953
9690
  });
8954
9691
  if (typeof value === "undefined") return jsx("span", {
8955
- className: css$29.otherValue,
9692
+ className: css$33.otherValue,
8956
9693
  children: "undefined"
8957
9694
  });
8958
9695
  return jsx("span", {
8959
- className: css$29.otherValue,
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$29.label, expandable && css$29.clickableLabel),
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$29.root}`);
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$29.stringField,
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$29.label,
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$29.stringRaw,
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$29.punctuation,
9808
+ className: css$33.punctuation,
9072
9809
  children: ","
9073
9810
  }),
9074
9811
  jsxs("div", {
9075
- className: css$29.stringActions,
9812
+ className: css$33.stringActions,
9076
9813
  children: [
9077
9814
  stringWrapping !== void 0 && jsx("button", {
9078
9815
  type: "button",
9079
- className: css$29.actionButton,
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$29.actionButton,
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$29.stringField,
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$29.stringText,
9861
+ className: css$33.stringText,
9125
9862
  children: [
9126
9863
  truncated && jsx("span", {
9127
- className: css$29.stringToggleSlot,
9864
+ className: css$33.stringToggleSlot,
9128
9865
  children: jsxs("button", {
9129
9866
  type: "button",
9130
- className: css$29.stringToggle,
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$29.label,
9882
+ className: css$33.label,
9146
9883
  children: [fieldText(field), ":"]
9147
9884
  }),
9148
9885
  primitiveValue(value),
9149
9886
  !lastElement && jsx("span", {
9150
- className: css$29.punctuation,
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$29.row,
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$29.punctuation,
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$29.punctuation,
9965
+ className: css$33.punctuation,
9229
9966
  children: open
9230
9967
  }),
9231
9968
  jsx("span", {
9232
- className: css$29.punctuation,
9969
+ className: css$33.punctuation,
9233
9970
  children: close
9234
9971
  }),
9235
9972
  !lastElement && jsx("span", {
9236
- className: css$29.punctuation,
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$29.expander, expanded ? css$29.collapseIcon : css$29.expandIcon),
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$29.summary,
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$29.preview,
10002
+ className: css$33.preview,
9266
10003
  children: previewValue(value, 0)
9267
10004
  }),
9268
10005
  !lastElement && jsx("span", {
9269
- className: css$29.punctuation,
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$29.children,
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$29.root, className),
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$29.expandedTopLevel,
10153
+ className: css$33.expandedTopLevel,
9417
10154
  children: [
9418
10155
  jsxs("div", {
9419
- className: clsx(css$29.row, css$29.topLevelBracket),
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$29.punctuation,
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$29.container, css$29.expandedTopLevelContainer),
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$29.row, css$29.topLevelBracket),
10193
+ className: clsx(css$33.row, css$33.topLevelBracket),
9457
10194
  children: jsx("span", {
9458
- className: css$29.punctuation,
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$29.container,
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 } = useCopyFeedback(text);
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$30.block, className),
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$30.header,
10746
+ className: css$34.header,
10010
10747
  children: [
10011
10748
  jsxs("div", {
10012
- className: css$30.prompt,
10749
+ className: css$34.prompt,
10013
10750
  children: [showState && jsx("span", {
10014
- className: css$30.runStateLabel,
10751
+ className: css$34.runStateLabel,
10015
10752
  children: state.label
10016
10753
  }), commandLines.map((line, index) => jsxs("div", {
10017
- className: css$30.promptLine,
10754
+ className: css$34.promptLine,
10018
10755
  children: [
10019
10756
  index === 0 && showState && jsx(StateDot, {
10020
10757
  state: state.state,
10021
- className: css$30.runState
10758
+ className: css$34.runState
10022
10759
  }),
10023
10760
  jsx("span", {
10024
- className: css$30.cwd,
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$30.command,
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$30.status,
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$30.copyButton,
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$30.empty,
10784
+ className: css$34.empty,
10048
10785
  children: copy.noOutput
10049
10786
  }) : jsxs("div", {
10050
- className: css$30.output,
10787
+ className: css$34.output,
10051
10788
  children: [
10052
10789
  (capped ? lines.slice(0, headLines) : lines).map((line, index) => jsx("div", {
10053
- className: css$30.line,
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$30.expand,
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$30.line,
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$31.line,
11390
+ className: css$35.line,
10654
11391
  children: [jsx("span", {
10655
- className: css$31.gutter,
11392
+ className: css$35.gutter,
10656
11393
  "aria-hidden": true,
10657
11394
  children: line.number
10658
11395
  }), jsx("span", {
10659
- className: css$31.content,
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$31.block, className),
11403
+ className: clsx(css$35.block, className),
10667
11404
  "data-read": "",
10668
11405
  children: [jsxs("div", {
10669
- className: css$31.banner,
11406
+ className: css$35.banner,
10670
11407
  children: [jsx("div", {
10671
- className: css$31.label,
11408
+ className: css$35.label,
10672
11409
  children: label ?? ""
10673
11410
  }), jsxs("div", {
10674
- className: css$31.action,
11411
+ className: css$35.action,
10675
11412
  children: [
10676
11413
  windowed && jsx("span", {
10677
- className: css$31.count,
11414
+ className: css$35.count,
10678
11415
  children: labels.window(lines.length, totalLines)
10679
11416
  }),
10680
11417
  jsx("span", {
10681
- className: css$31.lang,
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$31.copyButton,
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$31.body,
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$31.expand,
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$32.path,
10720
- del: css$32.del,
10721
- add: css$32.add,
10722
- context: css$32.context,
10723
- gap: css$32.gap
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$32.block, className),
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$32.copyButton,
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$32.body,
11608
+ className: css$36.body,
10872
11609
  children: [
10873
11610
  head.map((row, index) => jsx("div", {
10874
- className: clsx(css$32.line, ROW_CLASS[row.kind]),
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$32.expand,
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$32.line, ROW_CLASS[row.kind]),
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$32.footer,
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 } = useCopyFeedback(copyText(props));
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$33.line,
11764
+ className: css$37.line,
11028
11765
  children: row.path
11029
11766
  });
11030
11767
  if (row.type === "match") return jsxs("div", {
11031
- className: css$33.line,
11768
+ className: css$37.line,
11032
11769
  children: [jsxs("span", {
11033
- className: css$33.lineNumber,
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$33.fileHeader,
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$33.filePath,
11782
+ className: css$37.filePath,
11046
11783
  children: row.path
11047
11784
  }), jsx("span", {
11048
- className: css$33.fileCount,
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$33.block, className),
11791
+ className: clsx(css$37.block, className),
11055
11792
  "data-search": props.kind,
11056
11793
  children: [jsxs("div", {
11057
- className: css$33.header,
11794
+ className: css$37.header,
11058
11795
  children: [jsx("span", {
11059
- className: css$33.summary,
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$33.copyButton,
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$33.empty,
11805
+ className: css$37.empty,
11069
11806
  children: props.labels.noResults
11070
11807
  }) : jsxs("div", {
11071
- className: css$33.body,
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$33.expand,
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
- /** Extend upstream dollar-only math syntax with TeX delimiters while reusing its token vocabulary. */
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, multiline) {
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 multiline ? effects.attempt(nonLazyContinuation, afterContinuation, nok)(code) : nok(code);
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 ? "sameLineDollarMathFlow" : "backslashMathFlow",
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, true);
11723
- const sameLineDollarMathFlow = createMathFlow(codes.dollarSign, codes.dollarSign, codes.dollarSign, false);
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]: sameLineDollarMathFlow
12469
+ [codes.dollarSign]: dollarMathFlow
11728
12470
  },
11729
12471
  text: { [codes.backslash]: backslashMathText }
11730
12472
  };
11731
12473
  /**
11732
- * TeX backslash delimiters and same-line display-dollar blocks as a micromark
11733
- * syntax extension reusing `micromark-extension-math`'s token vocabulary; the
11734
- * caller must also register `math()` on the same parse so the emitted tokens
11735
- * compile to standard math nodes.
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 `&amp;` 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
- mathCompatibility(),
11776
- math()
12559
+ math(),
12560
+ mathCompatibility()
11777
12561
  ],
11778
- mdastExtensions: [gfmFromMarkdown(), mathFromMarkdown()]
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 sourceLines = lineNumbers ? trimmed.split("\n") : void 0;
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$34.plain,
11905
- children: jsx("code", { children: sourceLines === void 0 ? trimmed : sourceLines.map((line, index) => jsxs(Fragment$1, { children: [index > 0 && "\n", jsx("span", {
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$34.block, "md-code-block", lineNumbers && css$34.numbered, className),
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$34.bannerWrap,
12705
+ className: css$38.bannerWrap,
11917
12706
  children: jsxs("div", {
11918
- className: css$34.banner,
12707
+ className: css$38.banner,
11919
12708
  "data-code-block-banner": true,
11920
12709
  children: [jsx("div", {
11921
- className: css$34.infostring,
12710
+ className: css$38.infostring,
11922
12711
  children: lang ?? ""
11923
12712
  }), jsx("div", {
11924
- className: css$34.action,
12713
+ className: css$38.action,
11925
12714
  children: jsx("button", {
11926
12715
  type: "button",
11927
- className: css$34.copyButton,
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$34.content,
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$35.stage,
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$35.error,
12880
+ className: css$39.error,
12092
12881
  role: "alert",
12093
12882
  children: [jsx("span", {
12094
- className: css$35.errorLine,
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$35.stage,
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$35.zoomOverlay,
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$35.zoomStage,
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
- * Decode a file destination and its optional GitHub-style line fragment.
12139
- * Literal `?` and `#` in filenames must be percent-encoded.
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 invalid line ranges.
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) || /^[\\/]{2}/.test(path) || /^[a-z][a-z\d+.-]*:/i.test(path) && !/^[a-z]:[\\/]/i.test(path)) return void 0;
12155
- if (hash < 0) return { path };
12156
- const fragment = value.slice(hash + 1);
12157
- const match = /^L([1-9]\d*)(?:-L([1-9]\d*))?$/.exec(fragment);
12158
- if (match === null) return void 0;
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 void 0;
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$36.backdrop,
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$36.stage,
13129
+ className: css$40.stage,
12324
13130
  children: [
12325
13131
  jsx("div", {
12326
- className: css$36.mask,
13132
+ className: css$40.mask,
12327
13133
  "aria-hidden": "true",
12328
13134
  onMouseDown: onClose
12329
13135
  }),
12330
13136
  jsx("img", {
12331
- className: css$36.image,
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$36.close,
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$37.frame,
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$37.image,
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$37.status,
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
- return rewritten === void 0 ? void 0 : vocabularyImageUrl(rewritten);
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 src = preview?.resolve(file.path);
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 src = (file === void 0 ? void 0 : fileImages?.resolve(file.path)) ?? imageSource(destination, pathImages);
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` and `#L24-L30` destinations (ranges open at their first line).
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$38.linkIcon
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$38.source,
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$38.sourceLink
14024
+ className: css$42.sourceLink
13175
14025
  }),
13176
14026
  source.snippet !== void 0 && source.snippet !== "" && jsx("div", {
13177
- className: css$38.snippet,
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$38.published,
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$38.block, className),
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$38.answer,
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$38.empty,
14057
+ className: css$42.empty,
13208
14058
  children: labels.noResults
13209
14059
  }) : jsx("ol", {
13210
- className: css$38.sources,
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$38.truncated,
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$38.block, css$38.fetch, className),
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$38.fetchUrl
14085
+ className: css$42.fetchUrl
13236
14086
  }), jsxs("div", {
13237
- className: css$38.fetchMeta,
14087
+ className: css$42.fetchMeta,
13238
14088
  children: [jsxs("span", {
13239
- className: css$38.status,
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$38.truncated,
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$39.root,
14248
+ className: css$44.root,
13281
14249
  children: [jsxs("button", {
13282
14250
  type: "button",
13283
- className: css$39.toggle,
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$39.body,
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$40.group,
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$40.start
14367
+ className: css$45.start
13389
14368
  }),
13390
14369
  jsx("div", {
13391
14370
  id: headingId,
13392
14371
  "data-menu-group-heading": "",
13393
- className: css$40.heading,
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, useCopyFeedback, useDismissOnOutsidePointer, useModalLayer, watchDarwinDesktop, writeClipboard };
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