@ptengine/inline-editor 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +286 -2
  3. package/dist/InlineEditor.d.ts +349 -0
  4. package/dist/InlineEditor.js +886 -0
  5. package/dist/PagePreview.d.ts +83 -0
  6. package/dist/PagePreview.js +111 -0
  7. package/dist/assets/AssetsPanel.d.ts +19 -0
  8. package/dist/assets/AssetsPanel.js +23 -0
  9. package/dist/assets/componentLibrary.d.ts +23 -0
  10. package/dist/assets/componentLibrary.js +12 -0
  11. package/dist/canvas-ui/index.d.ts +17 -0
  12. package/dist/canvas-ui/index.js +7 -0
  13. package/dist/canvas-ui/scope.d.ts +13 -0
  14. package/dist/canvas-ui/scope.js +1 -0
  15. package/dist/canvas-ui/selection/classNames.d.ts +30 -0
  16. package/dist/canvas-ui/selection/classNames.js +15 -0
  17. package/dist/canvas-ui/selection/tokens.css +204 -0
  18. package/dist/canvas-ui/tree/TreePanel.d.ts +47 -0
  19. package/dist/canvas-ui/tree/TreePanel.js +132 -0
  20. package/dist/canvas-ui/tree/classNames.d.ts +21 -0
  21. package/dist/canvas-ui/tree/classNames.js +8 -0
  22. package/dist/canvas-ui/tree/order.d.ts +38 -0
  23. package/dist/canvas-ui/tree/order.js +38 -0
  24. package/dist/canvas-ui/tree/tree.css +131 -0
  25. package/dist/chrome/EditorChrome.d.ts +83 -0
  26. package/dist/chrome/EditorChrome.js +10 -0
  27. package/dist/chrome/VersionMenu.d.ts +26 -0
  28. package/dist/chrome/VersionMenu.js +86 -0
  29. package/dist/chrome/topbarPrimitives.d.ts +69 -0
  30. package/dist/chrome/topbarPrimitives.js +39 -0
  31. package/dist/chrome/useDismissOnOutside.d.ts +23 -0
  32. package/dist/chrome/useDismissOnOutside.js +27 -0
  33. package/dist/dialogs/ClickActionDialog.d.ts +24 -0
  34. package/dist/dialogs/ClickActionDialog.js +35 -0
  35. package/dist/dialogs/HtmlEditDialog.d.ts +23 -0
  36. package/dist/dialogs/HtmlEditDialog.js +23 -0
  37. package/dist/dialogs/ImageEditDialog.d.ts +30 -0
  38. package/dist/dialogs/ImageEditDialog.js +57 -0
  39. package/dist/dialogs/ImageSettingDialog.d.ts +16 -0
  40. package/dist/dialogs/ImageSettingDialog.js +17 -0
  41. package/dist/dialogs/InsertElementDialog.d.ts +23 -0
  42. package/dist/dialogs/InsertElementDialog.js +28 -0
  43. package/dist/dialogs/ReplaceVideoDialog.d.ts +18 -0
  44. package/dist/dialogs/ReplaceVideoDialog.js +16 -0
  45. package/dist/dialogs/actionTypes.d.ts +53 -0
  46. package/dist/dialogs/actionTypes.js +74 -0
  47. package/dist/dialogs/htmlValidation.d.ts +24 -0
  48. package/dist/dialogs/htmlValidation.js +37 -0
  49. package/dist/dialogs/imageEdit.d.ts +49 -0
  50. package/dist/dialogs/imageEdit.js +30 -0
  51. package/dist/index.d.ts +101 -0
  52. package/dist/index.js +53 -0
  53. package/dist/kernel/CanvasContextBar.d.ts +23 -0
  54. package/dist/kernel/CanvasContextBar.js +12 -0
  55. package/dist/kernel/CanvasFrame.d.ts +56 -0
  56. package/dist/kernel/CanvasFrame.js +53 -0
  57. package/dist/kernel/ai/aiInlineApply.d.ts +330 -0
  58. package/dist/kernel/ai/aiInlineApply.js +477 -0
  59. package/dist/kernel/ai/imageSource.d.ts +24 -0
  60. package/dist/kernel/ai/imageSource.js +19 -0
  61. package/dist/kernel/ai/insertHtmlGate.d.ts +71 -0
  62. package/dist/kernel/ai/insertHtmlGate.js +123 -0
  63. package/dist/kernel/ai/postApplyCheck.d.ts +56 -0
  64. package/dist/kernel/ai/postApplyCheck.js +74 -0
  65. package/dist/kernel/ai/replaceHtmlGate.d.ts +71 -0
  66. package/dist/kernel/ai/replaceHtmlGate.js +147 -0
  67. package/dist/kernel/blocks/BlockOverlay.d.ts +16 -0
  68. package/dist/kernel/blocks/BlockOverlay.js +66 -0
  69. package/dist/kernel/blocks/annotateAndTile.d.ts +106 -0
  70. package/dist/kernel/blocks/annotateAndTile.js +122 -0
  71. package/dist/kernel/blocks/resolveBlocks.d.ts +153 -0
  72. package/dist/kernel/blocks/resolveBlocks.js +233 -0
  73. package/dist/kernel/canvasCapabilities.d.ts +31 -0
  74. package/dist/kernel/canvasCapabilities.js +39 -0
  75. package/dist/kernel/canvasCommands.d.ts +126 -0
  76. package/dist/kernel/canvasCommands.js +146 -0
  77. package/dist/kernel/canvasErrors.d.ts +1 -0
  78. package/dist/kernel/canvasErrors.js +14 -0
  79. package/dist/kernel/captureGeometry.d.ts +35 -0
  80. package/dist/kernel/captureGeometry.js +25 -0
  81. package/dist/kernel/captureOrchestration.d.ts +48 -0
  82. package/dist/kernel/captureOrchestration.js +43 -0
  83. package/dist/kernel/captureSlices.d.ts +36 -0
  84. package/dist/kernel/captureSlices.js +20 -0
  85. package/dist/kernel/changelist/ChangeListPanel.d.ts +13 -0
  86. package/dist/kernel/changelist/ChangeListPanel.js +47 -0
  87. package/dist/kernel/changelist/ChangeListPopover.d.ts +20 -0
  88. package/dist/kernel/changelist/ChangeListPopover.js +10 -0
  89. package/dist/kernel/changelist/builders.d.ts +89 -0
  90. package/dist/kernel/changelist/builders.js +81 -0
  91. package/dist/kernel/changelist/changeSummary.d.ts +19 -0
  92. package/dist/kernel/changelist/changeSummary.js +85 -0
  93. package/dist/kernel/changelist/clipboard.d.ts +16 -0
  94. package/dist/kernel/changelist/clipboard.js +4 -0
  95. package/dist/kernel/changelist/exportForSave.d.ts +30 -0
  96. package/dist/kernel/changelist/exportForSave.js +11 -0
  97. package/dist/kernel/changelist/mergeForSave.d.ts +79 -0
  98. package/dist/kernel/changelist/mergeForSave.js +40 -0
  99. package/dist/kernel/changelist/moveObserverSafety.d.ts +70 -0
  100. package/dist/kernel/changelist/moveObserverSafety.js +39 -0
  101. package/dist/kernel/changelist/overrides.d.ts +22 -0
  102. package/dist/kernel/changelist/overrides.js +63 -0
  103. package/dist/kernel/changelist/useChangeList.d.ts +200 -0
  104. package/dist/kernel/changelist/useChangeList.js +282 -0
  105. package/dist/kernel/config.d.ts +29 -0
  106. package/dist/kernel/config.js +10 -0
  107. package/dist/kernel/dataUrlToBlob.d.ts +8 -0
  108. package/dist/kernel/dataUrlToBlob.js +17 -0
  109. package/dist/kernel/linkHref.d.ts +25 -0
  110. package/dist/kernel/linkHref.js +16 -0
  111. package/dist/kernel/mobileFit.d.ts +46 -0
  112. package/dist/kernel/mobileFit.js +44 -0
  113. package/dist/kernel/moveState.d.ts +35 -0
  114. package/dist/kernel/moveState.js +14 -0
  115. package/dist/kernel/selection/SelectionOverlay.d.ts +10 -0
  116. package/dist/kernel/selection/SelectionOverlay.js +25 -0
  117. package/dist/kernel/selection/SelectionToolbar.d.ts +30 -0
  118. package/dist/kernel/selection/SelectionToolbar.js +82 -0
  119. package/dist/kernel/selection/SelectorPathBar.d.ts +27 -0
  120. package/dist/kernel/selection/SelectorPathBar.js +55 -0
  121. package/dist/kernel/selection/buildDescendantShelf.d.ts +40 -0
  122. package/dist/kernel/selection/buildDescendantShelf.js +50 -0
  123. package/dist/kernel/selection/buildRootShelf.d.ts +26 -0
  124. package/dist/kernel/selection/buildRootShelf.js +149 -0
  125. package/dist/kernel/selection/elementAiContext.d.ts +324 -0
  126. package/dist/kernel/selection/elementAiContext.js +122 -0
  127. package/dist/kernel/selection/elementGeometryForAi.d.ts +9 -0
  128. package/dist/kernel/selection/elementGeometryForAi.js +15 -0
  129. package/dist/kernel/selection/geometry.d.ts +55 -0
  130. package/dist/kernel/selection/geometry.js +18 -0
  131. package/dist/kernel/selection/resolveAnchors.d.ts +116 -0
  132. package/dist/kernel/selection/resolveAnchors.js +173 -0
  133. package/dist/kernel/selection/shelfRow.d.ts +33 -0
  134. package/dist/kernel/selection/shelfRow.js +21 -0
  135. package/dist/kernel/selection/tagLabels.d.ts +9 -0
  136. package/dist/kernel/selection/tagLabels.js +46 -0
  137. package/dist/kernel/selection/toolbarButtons.d.ts +60 -0
  138. package/dist/kernel/selection/toolbarButtons.js +105 -0
  139. package/dist/kernel/selection/useAiSelectionContext.d.ts +26 -0
  140. package/dist/kernel/selection/useAiSelectionContext.js +26 -0
  141. package/dist/kernel/selection/useCanvasGeometry.d.ts +32 -0
  142. package/dist/kernel/selection/useCanvasGeometry.js +47 -0
  143. package/dist/kernel/selection/useSelectionRestore.d.ts +18 -0
  144. package/dist/kernel/selection/useSelectionRestore.js +25 -0
  145. package/dist/kernel/shrinkImageForUpload.d.ts +45 -0
  146. package/dist/kernel/shrinkImageForUpload.js +61 -0
  147. package/dist/kernel/useCanvasBridge.d.ts +36 -0
  148. package/dist/kernel/useCanvasBridge.js +41 -0
  149. package/dist/kernel/useCanvasGate.d.ts +22 -0
  150. package/dist/kernel/useCanvasGate.js +35 -0
  151. package/dist/kernel/useCanvasSession.d.ts +44 -0
  152. package/dist/kernel/useCanvasSession.js +182 -0
  153. package/dist/kernel/useCanvasStream.d.ts +39 -0
  154. package/dist/kernel/useCanvasStream.js +45 -0
  155. package/dist/kernel/useMobileFit.d.ts +40 -0
  156. package/dist/kernel/useMobileFit.js +139 -0
  157. package/dist/locales.d.ts +1094 -0
  158. package/dist/locales.js +545 -0
  159. package/dist/panels/StylePanel.d.ts +24 -0
  160. package/dist/panels/StylePanel.js +40 -0
  161. package/dist/panels/styleFields.d.ts +39 -0
  162. package/dist/panels/styleFields.js +91 -0
  163. package/dist/tree/LayersPanel.d.ts +30 -0
  164. package/dist/tree/LayersPanel.js +15 -0
  165. package/dist/tree/domTreeAdapter.d.ts +63 -0
  166. package/dist/tree/domTreeAdapter.js +90 -0
  167. package/dist/useInlineEditor.d.ts +161 -0
  168. package/dist/useInlineEditor.js +346 -0
  169. package/package.json +58 -3
@@ -0,0 +1,83 @@
1
+ import type { CanvasDevice } from '@ptengine/inline-core/protocol';
2
+ import type { PageModelBlock } from './kernel/blocks/resolveBlocks.js';
3
+ /**
4
+ * 分析阶段的**被动预览板**:客户页嵌在左边、用户在右边聊天,这块板子只负责
5
+ * 「让人看见这一页,并看见 agent 说的每个区块在哪」。
6
+ *
7
+ * ## 它为什么是一个独立组件,而不是 `<InlineEditor chrome="minimal">`
8
+ *
9
+ * `chrome` 只换顶栏 —— 图层面板、改动清单、选中框、选中工具条、属性面板、一堆弹框
10
+ * 仍然全都在渲染。那些部件在分析阶段一个都用不上:这里**没有改动**可做,出现一个
11
+ * 「编辑样式」按钮只会让人以为自己可以改,点下去还真能改,改完又没人存。
12
+ *
13
+ * 而给 `<InlineEditor>` 加 `showLayers` / `showToolbar` / `showChangeList` 这类开关,
14
+ * 正是包头部 API 主张明令不走的路(见 `index.ts`):外壳差异归外壳,内核归 headless 层。
15
+ * 所以这里直接用 `useInlineEditor()` 自己拼,**只挂三件东西**:顶栏、画布、区块框。
16
+ *
17
+ * ## 「被动」是怎么做到的 —— 不是靠关掉拾取
18
+ *
19
+ * ⚠️ 拾取(pick mode)**必须开着**,这块板子的惰性正是它给的:`picker.ts` 的
20
+ * `onPointerDown`/`onClick` 在客户页上 `preventDefault`/`stopPropagation`,用户在页面上
21
+ * 点到链接、按钮、表单都不会真的触发。关掉拾取,嵌进来的客户页就**活了** —— 点一个
22
+ * 链接画布自己导航走,而 sessionId 不变,于是区块坐标、几何、截图全部对着一个已经
23
+ * 不在那儿的页面,且零报错。
24
+ *
25
+ * 拾取开着的代价只是 `elementPicked` 事件没人消费(`useInlineEditor` 会照常刷一次选中
26
+ * 态),而我们不渲染任何选中 UI —— 无害。
27
+ *
28
+ * ## 数据只关掉了一处
29
+ *
30
+ * `tree: false`:图层树是一次跨三跳往返 + 整棵结构的序列化(depth 16 / maxNodes 1500),
31
+ * 这块板子一行都用不到。
32
+ */
33
+ export interface PagePreviewHandle {
34
+ /** 整页截图(body)→ PNG Blob。**不上传** —— 上传是宿主的事。 */
35
+ capturePage(): Promise<Blob>;
36
+ /** 把某个元素滚进视野(宿主点聊天里的区块时用)。未命中不抛错。 */
37
+ scrollToSelector(selector: string): Promise<void>;
38
+ /**
39
+ * 当前**已就绪**会话的 id;未就绪(含切设备后新 frame 还没报 domReady)为 null。
40
+ * 宿主拿它调 `resolveBlocks` —— 那批命令必须打在一个 agent 真的在听的会话上。
41
+ */
42
+ getSessionId(): string | null;
43
+ /**
44
+ * 画布当前施加在 `<html>` 上的缩放比例(移动端超宽缩小;不缩为 1)。宿主调 `resolveBlocks`
45
+ * 与截图标注时要用它把热图坐标对到缩放后的页面上。把手在首轮缩放落地**之后**才交出,
46
+ * 所以拿到把手时这个值已经是准的。
47
+ */
48
+ getMobileFitScale(): number;
49
+ }
50
+ export interface PagePreviewProps {
51
+ url: string;
52
+ device: CanvasDevice;
53
+ /**
54
+ * 按设备覆盖目标视口宽(只对**这一个实例**生效,合在 `TARGET_VIEWPORT_WIDTH` 之上)。
55
+ * 分析画布传 `{ mobile: 375 }`:热图区块的坐标是按 375 宽量的,画布默认 390 的话
56
+ * 区块框会与页面差一截。
57
+ *
58
+ * 命名与 `<InlineEditor>` 的同名 prop 一致 —— 同一件事在两个外壳上不该有两个名字。
59
+ */
60
+ viewportWidthOverride?: {
61
+ pc?: number;
62
+ mobile?: number;
63
+ };
64
+ /** 要画的区块(文档坐标,来自 `resolveBlocks`)。 */
65
+ blocks: PageModelBlock[];
66
+ /** 高亮哪一块(宿主把聊天里正在讲的那段与画布对上)。 */
67
+ activeBlockId?: string | null;
68
+ /** 鼠标移到区块芯片上/移开;宿主据此反向高亮聊天里的对应段。 */
69
+ onHoverBlock?: (id: string | null) => void;
70
+ /** 顶栏主标题,通常是 host + path。 */
71
+ title?: string;
72
+ /** 顶栏副标题,通常是 agent 给的选页理由(`reason`)。 */
73
+ subtitle?: string;
74
+ /** 不传则**不渲染**关闭按钮 —— 点了没反应的按钮比没有按钮更糟。 */
75
+ onClose?: () => void;
76
+ /** 不传则不渲染设备开关(同上)。 */
77
+ onDeviceChange?: (device: CanvasDevice) => void;
78
+ /** 每个会话就绪时触发一次(切设备开新会话会再触发)。 */
79
+ onSessionReady?: (sessionId: string) => void;
80
+ /** 把手交给宿主,同样每个会话一次。 */
81
+ onReady?: (handle: PagePreviewHandle) => void;
82
+ }
83
+ export declare function PagePreview({ url, device, viewportWidthOverride, blocks, activeBlockId, onHoverBlock, title, subtitle, onClose, onDeviceChange, onSessionReady, onReady }: PagePreviewProps): import("react").JSX.Element;
@@ -0,0 +1,111 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useMemo, useRef, useState } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Monitor, Smartphone, X } from 'lucide-react';
5
+ import { CANVAS_UI_SCOPE } from './canvas-ui/index.js';
6
+ import { CanvasFrame, TARGET_VIEWPORT_WIDTH, computeZoom } from './kernel/CanvasFrame.js';
7
+ import { BlockOverlay } from './kernel/blocks/BlockOverlay.js';
8
+ import { measureFrameContext } from './kernel/selection/geometry.js';
9
+ import { describeCanvasError } from './kernel/canvasErrors.js';
10
+ import { dataUrlToBlob } from './kernel/dataUrlToBlob.js';
11
+ import { useInlineEditor } from './useInlineEditor.js';
12
+ export function PagePreview({ url, device, viewportWidthOverride, blocks, activeBlockId, onHoverBlock, title, subtitle, onClose, onDeviceChange, onSessionReady, onReady }) {
13
+ const { t } = useTranslation();
14
+ const canvasAreaRef = useRef(null);
15
+ const iframeRef = useRef(null);
16
+ const editor = useInlineEditor({
17
+ url,
18
+ device,
19
+ iframeRef,
20
+ tree: false
21
+ });
22
+ const { session, frameProps, domReady, geometry, capturing, commands, mobileFit } = editor;
23
+ const sessionId = frameProps?.sessionId;
24
+ const ready = domReady && !mobileFit.pending;
25
+ const [panelWidth, setPanelWidth] = useState(0);
26
+ const [layoutTick, setLayoutTick] = useState(0);
27
+ useEffect(() => {
28
+ const el = canvasAreaRef.current;
29
+ if (!el)
30
+ return;
31
+ const ro = new ResizeObserver(entries => {
32
+ const w = entries[0]?.contentRect.width;
33
+ if (w)
34
+ setPanelWidth(w);
35
+ setLayoutTick(n => n + 1);
36
+ });
37
+ ro.observe(el);
38
+ return () => ro.disconnect();
39
+ }, []);
40
+ const targetViewportWidth = useMemo(() => ({
41
+ pc: viewportWidthOverride?.pc ?? TARGET_VIEWPORT_WIDTH.pc,
42
+ mobile: viewportWidthOverride?.mobile ?? TARGET_VIEWPORT_WIDTH.mobile
43
+ }), [viewportWidthOverride?.pc, viewportWidthOverride?.mobile]);
44
+ const targetWidth = targetViewportWidth[device];
45
+ const zoom = computeZoom(panelWidth, targetWidth);
46
+ const iframeRect = iframeRef.current?.getBoundingClientRect();
47
+ const overlayContext = useMemo(() => measureFrameContext(iframeRef.current, zoom), [iframeRect?.left, iframeRect?.top, zoom, layoutTick, sessionId, domReady]);
48
+ const kickedForRef = useRef(null);
49
+ const hover = commands.hover;
50
+ useEffect(() => {
51
+ if (!domReady || !sessionId)
52
+ return;
53
+ if (kickedForRef.current === sessionId)
54
+ return;
55
+ kickedForRef.current = sessionId;
56
+ void hover('').catch(() => {
57
+ });
58
+ }, [domReady, sessionId, hover]);
59
+ const commandsRef = useRef(commands);
60
+ commandsRef.current = commands;
61
+ const readySessionRef = useRef(null);
62
+ readySessionRef.current = ready && sessionId ? sessionId : null;
63
+ const fitScaleRef = useRef(1);
64
+ fitScaleRef.current = mobileFit.scale;
65
+ const sessionReadyRef = useRef(null);
66
+ useEffect(() => {
67
+ if (!onSessionReady || !ready || !sessionId)
68
+ return;
69
+ if (sessionReadyRef.current === sessionId)
70
+ return;
71
+ sessionReadyRef.current = sessionId;
72
+ onSessionReady(sessionId);
73
+ }, [onSessionReady, ready, sessionId]);
74
+ const handleSessionRef = useRef(null);
75
+ useEffect(() => {
76
+ if (!onReady || !ready || !sessionId)
77
+ return;
78
+ if (handleSessionRef.current === sessionId)
79
+ return;
80
+ handleSessionRef.current = sessionId;
81
+ onReady({
82
+ capturePage: async () => {
83
+ const outcome = await commandsRef.current.captureSelectedElement('body');
84
+ return dataUrlToBlob(outcome.dataUrl);
85
+ },
86
+ scrollToSelector: async (selector) => {
87
+ await commandsRef.current.reveal(selector);
88
+ },
89
+ getSessionId: () => readySessionRef.current,
90
+ getMobileFitScale: () => fitScaleRef.current
91
+ });
92
+ }, [onReady, ready, sessionId]);
93
+ const failed = session.state === 'failed';
94
+ const statusText = failed
95
+ ? t('inlineCanvas.preview.failed', '页面加载失败')
96
+ : domReady
97
+ ? t('inlineCanvas.preview.ready', '页面已就绪')
98
+ : t('inlineCanvas.preview.loading', '正在加载页面…');
99
+ return (_jsxs("div", { "data-testid": "page-preview", className: `${CANVAS_UI_SCOPE} flex h-full min-h-0 flex-col bg-[#F5F7FC]`, children: [_jsxs("header", { "data-testid": "page-preview-chrome", className: "flex h-12 shrink-0 items-center justify-between gap-3 border-b\n border-[#EBECF0] bg-white px-3", children: [_jsxs("div", { className: "flex min-w-0 flex-col justify-center", children: [_jsx("span", { "data-testid": "page-preview-title", className: "truncate text-[13px] font-semibold leading-4 text-[#091E42]", children: title ?? url }), subtitle && (_jsx("span", { "data-testid": "page-preview-subtitle", className: "truncate text-[11px] leading-4 text-[#6B778C]", children: subtitle }))] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [onDeviceChange && (_jsx("div", { "data-testid": "page-preview-device-switch", className: "flex gap-0.5 rounded bg-[#F4F5F7] p-0.5", children: [
100
+ ['pc', Monitor, t('inlineCanvas.chrome.pc', 'PC')],
101
+ ['mobile', Smartphone, t('inlineCanvas.chrome.mobile', '移动')]
102
+ ].map(([value, Icon, label]) => {
103
+ const on = device === value;
104
+ return (_jsxs("button", { type: "button", "data-testid": `page-preview-device-${value}`, "aria-pressed": on, onClick: () => onDeviceChange(value), className: `flex items-center gap-1.5 rounded px-2.5 py-1
105
+ text-xs ${on
106
+ ? 'bg-white font-semibold text-[#091E42]'
107
+ : 'text-[#8993A4]'}`, children: [_jsx(Icon, { className: "size-3.5" }), label] }, value));
108
+ }) })), onClose && (_jsx("button", { type: "button", "data-testid": "page-preview-close", "aria-label": t('inlineCanvas.chrome.close', '关闭'), onClick: onClose, className: "flex size-7 items-center justify-center rounded\n text-[#6B778C] hover:bg-[#F4F5F7]", children: _jsx(X, { className: "size-4" }) }))] })] }), _jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-2 p-3", children: [_jsx("div", { ref: canvasAreaRef, className: "relative min-h-0 flex-1 overflow-hidden rounded border\n border-[#DFE1E6] bg-white", children: failed ? (_jsxs("div", { "data-testid": "page-preview-failed", className: "flex h-full flex-col items-center justify-center gap-1 px-6\n text-center", children: [_jsx("p", { className: "text-[13px] font-semibold text-[#DE350B]", children: t('inlineCanvas.preview.failed', '页面加载失败') }), _jsx("p", { className: "text-[11px] leading-4 text-[#6B778C]", children: t('inlineCanvas.preview.failedHint', '请确认已安装并启用 Ptengine 插件,且该页面允许被嵌入') }), session.reason && (_jsx("code", { "data-testid": "page-preview-failed-reason", className: "text-[11px] leading-4 text-[#8993A4]", children: describeCanvasError(session.reason) }))] })) : frameProps ? (_jsx(CanvasFrame, { url: frameProps.url, sessionId: frameProps.sessionId, device: frameProps.device, panelWidth: panelWidth, targetViewportWidth: targetViewportWidth[frameProps.device], onFrameLoad: () => { }, domReady: domReady, iframeRef: iframeRef }, frameProps.sessionId)) : (_jsx("div", { "data-testid": "page-preview-loading", className: "flex h-full items-center justify-center text-[12px]\n text-[#6B778C]", children: t('inlineCanvas.preview.loading', '正在加载页面…') })) }), _jsxs("div", { "data-testid": "page-preview-status", className: "flex h-5 shrink-0 items-center gap-2 text-[11px] leading-4\n text-[#6B778C]", children: [_jsx("span", { "data-testid": "page-preview-status-text", children: statusText }), !failed && blocks.length > 0 && (_jsx("span", { "data-testid": "page-preview-status-blocks", children: t('inlineCanvas.preview.blockCount', '{{count}} 个区块', {
109
+ count: blocks.length
110
+ }) })), _jsxs("span", { "data-testid": "page-preview-status-debug", style: { marginLeft: 'auto' }, children: ["dbg seq ", geometry.latest?.frameSeq ?? '-', " \u00B7 y", ' ', geometry.latest?.scrollY ?? '-', " \u00B7 in ", geometry.debug.received, " \u00B7 drop", ' ', geometry.debug.dropped, " \u00B7 zoom ", zoom, " \u00B7 vh", ' ', geometry.latest?.viewportHeight ?? '-'] })] })] }), !capturing && frameProps && (_jsx(BlockOverlay, { blocks: blocks, activeId: activeBlockId, onHover: onHoverBlock, geometry: geometry.latest, context: overlayContext, scale: mobileFit.scale }))] }));
111
+ }
@@ -0,0 +1,19 @@
1
+ import type { LibraryComponent } from './componentLibrary.js';
2
+ /**
3
+ * 组件库面板(左侧「组件」页签)。
4
+ *
5
+ * ⚠️ **组件数据由宿主注入**,包里不调 GraphQL —— 与 `uploadImage` 同一套分工:
6
+ * 包不知道后端在哪、不知道 profileId。
7
+ *
8
+ * 点一个组件即插入到当前选中元素内部末尾。**没有拖拽到画布** —— 那需要在跨域 iframe 上做
9
+ * 命中测试再算落点,而落点又要换算成 `elementContent` 的 mode;本期先用「选中 + 点击」
10
+ * 这条已经通的路,不为一个交互糖引入一整套坐标换算。
11
+ */
12
+ export interface AssetsPanelProps {
13
+ components: LibraryComponent[];
14
+ loading?: boolean;
15
+ /** 没有选中元素时禁用插入 —— 插到哪都不知道。 */
16
+ canInsert: boolean;
17
+ onInsert: (component: LibraryComponent) => void;
18
+ }
19
+ export declare function AssetsPanel({ components, loading, canInsert, onInsert }: AssetsPanelProps): import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from 'react';
3
+ import { Input } from '@ptengine/design-components';
4
+ export function AssetsPanel({ components, loading, canInsert, onInsert }) {
5
+ const [query, setQuery] = useState('');
6
+ const groups = useMemo(() => {
7
+ const q = query.trim().toLowerCase();
8
+ const matched = q
9
+ ? components.filter(c => c.name.toLowerCase().includes(q))
10
+ : components;
11
+ const byCategory = new Map();
12
+ for (const c of matched) {
13
+ const key = c.category?.trim() || '未分类';
14
+ const list = byCategory.get(key);
15
+ if (list)
16
+ list.push(c);
17
+ else
18
+ byCategory.set(key, [c]);
19
+ }
20
+ return [...byCategory.entries()];
21
+ }, [components, query]);
22
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", "data-testid": "assets-panel", children: [_jsx("div", { className: "p-2", children: _jsx(Input, { "data-testid": "assets-search", placeholder: "\u641C\u7D22\u7EC4\u4EF6", value: query, onChange: e => setQuery(e.target.value) }) }), !canInsert && (_jsx("p", { "data-testid": "assets-need-selection", className: "px-3 pb-2 text-[11px] leading-4 text-[#6B778C]", children: "\u5148\u5728\u753B\u5E03\u91CC\u9009\u4E2D\u4E00\u4E2A\u5143\u7D20\uFF0C\u7EC4\u4EF6\u4F1A\u63D2\u5230\u5B83\u5185\u90E8" })), _jsx("div", { className: "min-h-0 flex-1 overflow-auto px-2 pb-2", children: loading ? (_jsx("p", { "data-testid": "assets-loading", className: "py-6 text-center text-xs text-[#8993A4]", children: "\u52A0\u8F7D\u4E2D\u2026" })) : groups.length === 0 ? (_jsx("p", { "data-testid": "assets-empty", className: "py-6 text-center text-xs text-[#8993A4]", children: query.trim() ? '没有匹配的组件' : '组件库是空的' })) : (groups.map(([category, list]) => (_jsxs("section", { className: "mb-3", children: [_jsx("p", { className: "mb-1.5 text-[11px] leading-4 text-[#6B778C]", children: category }), _jsx("div", { className: "grid grid-cols-2 gap-1.5", children: list.map(component => (_jsxs("button", { type: "button", "data-testid": `asset-${component.id}`, disabled: !canInsert, title: component.name, onClick: () => onInsert(component), className: "flex flex-col items-stretch gap-1 rounded border\n border-[#DFE1E6] p-1.5 text-left hover:border-[#00A92E]\n disabled:cursor-not-allowed disabled:opacity-50", children: [component.thumbnail ? (_jsx("img", { src: component.thumbnail, alt: "", className: "h-14 w-full rounded object-cover" })) : (_jsx("span", { className: "flex h-14 items-center justify-center\n rounded bg-[#F4F5F7] text-[10px] text-[#8993A4]", children: "\u65E0\u9884\u89C8" })), _jsx("span", { className: "truncate text-[11px] text-[#344563]", children: component.name })] }, component.id))) })] }, category)))) })] }));
23
+ }
@@ -0,0 +1,23 @@
1
+ import type { ExperienceCode } from '@ptengine/inline-core/protocol';
2
+ /**
3
+ * 库组件 → 插入所需的 HTML + 资产。
4
+ *
5
+ * ⚠️ **css/js 必须一起交出去**。不注入的话组件在画布里就是个没样式、不工作的空壳,
6
+ * 而用户会以为「插入失败」。资产走既有的 `setCode` 命令注入客户页。
7
+ *
8
+ * ⚠️ 这只解决**画布预览**。线上要生效还需要发布链路把组件资产带上 —— 那不在本包职责内,
9
+ * 但接入方必须知道,否则会出现「画布里好的、上线没了」。
10
+ */
11
+ export interface LibraryComponent {
12
+ id: string;
13
+ name: string;
14
+ category?: string | null;
15
+ thumbnail?: string | null;
16
+ htmlTemplate: string;
17
+ css?: string | null;
18
+ js?: string | null;
19
+ }
20
+ export declare function componentInsertPayload(component: LibraryComponent): {
21
+ html: string;
22
+ codeList: ExperienceCode[];
23
+ };
@@ -0,0 +1,12 @@
1
+ import { pasteHtml } from '../kernel/changelist/clipboard.js';
2
+ export function componentInsertPayload(component) {
3
+ const codeList = [];
4
+ if (component.css)
5
+ codeList.push({ type: 'css', code: component.css });
6
+ if (component.js)
7
+ codeList.push({ type: 'javascript', code: component.js });
8
+ return {
9
+ html: pasteHtml({ html: component.htmlTemplate }),
10
+ codeList
11
+ };
12
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * ⚠️ **样式必须由这里统一带出**。
3
+ *
4
+ * class 名对得上但 CSS 没加载时,元素会渲染出来毫无外观 —— 现象是「框不见了」,
5
+ * 而排查时会一直怀疑是 JS 没渲染(我们在接线时就踩了一次:两个 CSS 文件写好了,
6
+ * 但没有任何地方 import 它们)。放在入口 import 而不是让每个消费方自己记得引,
7
+ * 就是为了让这件事不可能被漏掉。
8
+ *
9
+ * 消费方(apps/x / vite)需要能处理 `.css` import —— 本仓的两个消费方都能。
10
+ *
11
+ * 样式只在 `CANVAS_UI_SCOPE` 之内生效(见 `scope.ts`):消费方要把它加在根节点上。
12
+ */
13
+ export * from './scope.js';
14
+ export * from './tree/order.js';
15
+ export * from './tree/classNames.js';
16
+ export * from './tree/TreePanel.js';
17
+ export * from './selection/classNames.js';
@@ -0,0 +1,7 @@
1
+ import './selection/tokens.css';
2
+ import './tree/tree.css';
3
+ export * from './scope.js';
4
+ export * from './tree/order.js';
5
+ export * from './tree/classNames.js';
6
+ export * from './tree/TreePanel.js';
7
+ export * from './selection/classNames.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * canvas-ui 全部样式的作用域根。两份 CSS 的每条规则都挂在它下面
3
+ * (`:where(.inline-editor-canvas) .select-box { … }`),`--lpv2-*` 变量也定义在它上面。
4
+ *
5
+ * ⚠️ 渲染本包 class 名的组件必须位于带这个 class 的元素之内,否则样式静默失效(「框不见了」)。
6
+ * 本仓的根:inline-editor 的 `InlineEditor` 与 `PagePreview`。
7
+ *
8
+ * 为什么必须有作用域:CSS 由包入口带出,而 apps/x 的 i18n 静态引用了 inline-editor 的总出口,
9
+ * 这两份 CSS 于是进了**全局**样式表;`.select-box` / `.layer-row` / `.marquee` 这些名字落地页
10
+ * 编辑器 v2/v3 也在用,没有作用域时本包独有的属性会漏进它们。用 `:where()` 是因为它不增加
11
+ * 优先级 —— 作用域内各条规则与 Tailwind 工具类之间的胜负关系一条都不变。
12
+ */
13
+ export declare const CANVAS_UI_SCOPE = "inline-editor-canvas";
@@ -0,0 +1 @@
1
+ export const CANVAS_UI_SCOPE = 'inline-editor-canvas';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 选中视觉的 class 契约 —— 与 v2 编辑器共用同一套名字(P-C2)。
3
+ *
4
+ * ⚠️ 这些字符串必须与 v2 `landing-page-editor-v2/styles/editor.css` 里的选择器逐字一致。
5
+ * 改名的后果是样式**静默失效** —— 元素还在 DOM 里,只是没有任何样式,
6
+ * 现象是「框不见了」,很难联想到是 class 名不对。有测试逐字锁住。
7
+ *
8
+ * 之所以做成常量而不是各处写字面量:手写 `'sel-tool-danger'` 这类字符串拼错时
9
+ * TypeScript 一声不响。
10
+ *
11
+ * ⚠️ **本期不含缩放手柄**(用户决定先不做)。理由不只是省事:客户页的尺寸多由
12
+ * flex/grid/百分比决定,`elementStyle` 写死 px 会在其它视口坏掉;而画出手柄却拖不动,
13
+ * 比没有手柄更让人困惑。将来要做,先回答「改尺寸产出什么 change」。
14
+ */
15
+ export declare const SELECTION_CLASS: {
16
+ readonly selectBox: "select-box";
17
+ readonly selectLabel: "select-label";
18
+ readonly selectSize: "select-size";
19
+ readonly hoverBox: "hover-box";
20
+ readonly hoverLabel: "hover-label";
21
+ readonly marquee: "marquee";
22
+ readonly toolbar: "sel-toolbar";
23
+ readonly tool: "sel-tool";
24
+ readonly toolDanger: "sel-tool-danger";
25
+ readonly toolAi: "sel-tool-ai";
26
+ readonly toolbarSep: "sel-toolbar-sep";
27
+ readonly overflowMenu: "sel-overflow-menu";
28
+ readonly overflowItem: "sel-overflow-item";
29
+ };
30
+ export type SelectionClassKey = keyof typeof SELECTION_CLASS;
@@ -0,0 +1,15 @@
1
+ export const SELECTION_CLASS = {
2
+ selectBox: 'select-box',
3
+ selectLabel: 'select-label',
4
+ selectSize: 'select-size',
5
+ hoverBox: 'hover-box',
6
+ hoverLabel: 'hover-label',
7
+ marquee: 'marquee',
8
+ toolbar: 'sel-toolbar',
9
+ tool: 'sel-tool',
10
+ toolDanger: 'sel-tool-danger',
11
+ toolAi: 'sel-tool-ai',
12
+ toolbarSep: 'sel-toolbar-sep',
13
+ overflowMenu: 'sel-overflow-menu',
14
+ overflowItem: 'sel-overflow-item'
15
+ };
@@ -0,0 +1,204 @@
1
+ /**
2
+ * 选中视觉的样式 token —— 与 v2 编辑器**同一套 class 名与观感**(P-C2)。
3
+ *
4
+ * 规则从 `apps/x/src/components/landing-page-editor-v2/styles/editor.css` **逐条移植**,
5
+ * 只做一处改动:v2 的作用域前缀 `.lp-editor-v2` 换成本包自己的 `:where(.inline-editor-canvas)`
6
+ * (`CANVAS_UI_SCOPE`,见 `scope.ts`),`--lpv2-*` 变量也定义在它上面而不是 `:root`。
7
+ * 这份 CSS 随包入口进了 apps/x 的**全局**样式表,而这些 class 名 v2/v3 也在用 —— 没有作用域,
8
+ * 本文件独有的属性会漏进它们。新规则同样要带这个前缀(有测试锁住)。
9
+ *
10
+ * ⚠️ 不要在这里"顺手改好看一点"。这个文件的唯一职责是让两个编辑器长得一样;
11
+ * 任何单方面的美化都会让它们开始漂移,而漂移是肉眼一眼看不出、只在并排对比时才发现的。
12
+ *
13
+ * ⚠️ 本期 v2 仍用它自己那份 CSS(不动线上),所以这里是**副本**。改动时要同步确认那边。
14
+ */
15
+
16
+ .inline-editor-canvas {
17
+ --lpv2-accent: #3b6fb0;
18
+ --lpv2-accent-dim: rgba(59, 111, 176, 0.45);
19
+ --lpv2-accent-soft: rgba(59, 111, 176, 0.1);
20
+ --lpv2-border: #e7e8e9;
21
+ --lpv2-danger: #cf3e3f;
22
+ --lpv2-danger-soft: rgba(207, 62, 63, 0.1);
23
+ --lpv2-guide: #ff6b6b;
24
+ --lpv2-hover: rgba(16, 28, 31, 0.08);
25
+ --lpv2-panel: #ffffff;
26
+ --lpv2-primary: #1c272a;
27
+ --lpv2-primary-text: #ffffff;
28
+ --lpv2-radius-lg: 10px;
29
+ --lpv2-radius-sm: 6px;
30
+ --lpv2-shadow-overlay: 0 8px 24px rgba(16, 28, 31, 0.14), 0 2px 8px rgba(16, 28, 31, 0.08);
31
+ --lpv2-text-body: rgba(16, 28, 31, 0.7);
32
+ }
33
+
34
+ :where(.inline-editor-canvas) .select-box {
35
+ position: absolute;
36
+ border: 1.5px solid var(--lpv2-accent);
37
+ z-index: 16;
38
+ }
39
+
40
+ :where(.inline-editor-canvas) .select-box-extra {
41
+ border-width: 1.5px;
42
+ }
43
+
44
+ :where(.inline-editor-canvas) .select-box.is-snapped {
45
+ border-color: var(--lpv2-guide);
46
+ }
47
+
48
+ :where(.inline-editor-canvas) .select-box.is-snapped .select-label {
49
+ background: var(--lpv2-guide);
50
+ }
51
+
52
+ :where(.inline-editor-canvas) .select-label {
53
+ position: absolute;
54
+ top: -24px;
55
+ left: -1.5px;
56
+ display: flex;
57
+ gap: 8px;
58
+ align-items: center;
59
+ padding: 2px 8px;
60
+ background: var(--lpv2-accent);
61
+ color: #ffffff;
62
+ border-radius: 4px;
63
+ font-size: 12px;
64
+ font-weight: 600;
65
+ white-space: nowrap;
66
+ }
67
+
68
+ :where(.inline-editor-canvas) .select-size {
69
+ font-weight: 400;
70
+ opacity: 0.85;
71
+ font-variant-numeric: tabular-nums;
72
+ }
73
+
74
+ :where(.inline-editor-canvas) .hover-box {
75
+ position: absolute;
76
+ border: 1.5px solid var(--lpv2-accent-dim);
77
+ z-index: 16;
78
+ }
79
+
80
+ :where(.inline-editor-canvas) .hover-label {
81
+ position: absolute;
82
+ top: -20px;
83
+ left: -1.5px;
84
+ padding: 1px 7px;
85
+ background: var(--lpv2-accent-dim);
86
+ color: #ffffff;
87
+ border-radius: 4px;
88
+ font-size: 12px;
89
+ font-weight: 600;
90
+ white-space: nowrap;
91
+ }
92
+
93
+ :where(.inline-editor-canvas) .marquee {
94
+ position: absolute;
95
+ border: 1px solid var(--lpv2-accent);
96
+ background: var(--lpv2-accent-soft);
97
+ pointer-events: none;
98
+ }
99
+
100
+ :where(.inline-editor-canvas) .sel-toolbar {
101
+ position: absolute;
102
+ top: -44px;
103
+ left: 50%;
104
+ transform: translateX(-50%);
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 2px;
108
+ padding: 4px;
109
+ background: var(--lpv2-panel);
110
+ border: 1px solid var(--lpv2-border);
111
+ border-radius: var(--lpv2-radius-lg);
112
+ box-shadow: var(--lpv2-shadow-overlay);
113
+ pointer-events: auto;
114
+ white-space: nowrap;
115
+ user-select: none;
116
+ /* 直接挂在覆盖层(非 select-box 内),z 高于 .section-add(25):吸顶时盖住贴在区块底边的绿 + */
117
+ z-index: 30;
118
+ }
119
+
120
+ :where(.inline-editor-canvas) .sel-toolbar-sep {
121
+ width: 1px;
122
+ height: 16px;
123
+ background: var(--lpv2-border);
124
+ margin: 0 2px;
125
+ }
126
+
127
+ :where(.inline-editor-canvas) .sel-toolbar-more-wrap {
128
+ position: relative;
129
+ display: inline-flex;
130
+ }
131
+
132
+ :where(.inline-editor-canvas) .sel-tool {
133
+ padding: 5px 9px;
134
+ background: transparent;
135
+ border: none;
136
+ border-radius: var(--lpv2-radius-sm);
137
+ color: var(--lpv2-text-body);
138
+ font-size: 12px;
139
+ cursor: pointer;
140
+ }
141
+
142
+ :where(.inline-editor-canvas) .sel-tool:hover {
143
+ background: var(--lpv2-hover);
144
+ }
145
+
146
+ :where(.inline-editor-canvas) .sel-tool:disabled {
147
+ opacity: 0.35;
148
+ cursor: default;
149
+ }
150
+
151
+ :where(.inline-editor-canvas) .sel-tool:disabled:hover {
152
+ background: transparent;
153
+ }
154
+
155
+ :where(.inline-editor-canvas) .sel-tool-ai {
156
+ background: var(--lpv2-primary);
157
+ color: var(--lpv2-primary-text);
158
+ font-weight: 600;
159
+ }
160
+
161
+ :where(.inline-editor-canvas) .sel-tool-ai:hover {
162
+ background: var(--lpv2-primary);
163
+ background-image: linear-gradient(rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.12));
164
+ }
165
+
166
+ :where(.inline-editor-canvas) .sel-tool-danger:hover {
167
+ background: var(--lpv2-danger-soft);
168
+ color: var(--lpv2-danger);
169
+ }
170
+
171
+ :where(.inline-editor-canvas) .sel-overflow-menu {
172
+ position: absolute;
173
+ top: calc(100% + 6px);
174
+ right: 0;
175
+ display: flex;
176
+ flex-direction: column;
177
+ gap: 2px;
178
+ padding: 4px;
179
+ background: var(--lpv2-panel);
180
+ border: 1px solid var(--lpv2-border);
181
+ border-radius: var(--lpv2-radius-lg);
182
+ box-shadow: var(--lpv2-shadow-overlay);
183
+ min-width: 140px;
184
+ z-index: 10;
185
+ }
186
+
187
+ :where(.inline-editor-canvas) .sel-overflow-item {
188
+ display: flex;
189
+ align-items: center;
190
+ gap: 8px;
191
+ padding: 6px 10px;
192
+ background: transparent;
193
+ border: none;
194
+ border-radius: var(--lpv2-radius-sm);
195
+ color: var(--lpv2-text-body);
196
+ font-size: 12px;
197
+ cursor: pointer;
198
+ text-align: left;
199
+ white-space: nowrap;
200
+ }
201
+
202
+ :where(.inline-editor-canvas) .sel-overflow-item:hover {
203
+ background: var(--lpv2-hover);
204
+ }
@@ -0,0 +1,47 @@
1
+ import type React from 'react';
2
+ import { type DropPos, type TreeNode } from './order.js';
3
+ /**
4
+ * 图层树展示层 —— **数据与回调全部注入**,不知道数据来自节点模型还是跨域 DOM 树。
5
+ *
6
+ * 这是「与 v2 保持同样交互」的落点:两边渲染同一个组件,行为不可能各自漂移。
7
+ * 反过来说,任何一方特有的东西(v2 的重命名、inline 的 selector)都不该进这里,
8
+ * 而应由调用方通过 props 表达。
9
+ */
10
+ export interface TreePanelNode extends TreeNode {
11
+ /** 行上显示的文字。 */
12
+ label: string;
13
+ /** 能否作为拖拽落点的容器 —— 决定 `inside` 档是否可用。 */
14
+ canContain: boolean;
15
+ /**
16
+ * 行首图标。**省略则不占位**(不留空白),因为两个消费方不一样:
17
+ * v2 图层树的节点自带图标,inline 的图标由宿主注入(glyph 真值表在 app 里,
18
+ * 包不能反向依赖 app)。
19
+ */
20
+ icon?: React.ReactNode;
21
+ /**
22
+ * 「底下还有东西,只是没加载出来」。可选 —— 数据源不做懒加载的消费方不用传。
23
+ *
24
+ * ⚠️ 与 `children.length > 0` 是**两件事**:截断节点的 children 恰恰是空的。
25
+ */
26
+ truncated?: boolean;
27
+ }
28
+ export interface TreePanelProps {
29
+ nodes: Record<string, TreePanelNode>;
30
+ rootId: string;
31
+ selectedId: string | null;
32
+ hoverId?: string | null;
33
+ /** 非空时按 label 过滤,且**忽略折叠态**。 */
34
+ query?: string;
35
+ onSelect: (id: string) => void;
36
+ onShiftSelect?: (ids: string[]) => void;
37
+ /** `null` = 清除高亮。 */
38
+ onHover: (id: string | null) => void;
39
+ /** 省略则行不可拖 —— 拖得动却什么都不发生是最糟的反馈。 */
40
+ onMove?: (dragId: string, targetId: string, pos: DropPos) => void;
41
+ /**
42
+ * 请求加载 `truncated` 节点的子树。**省略则截断节点不出箭头** —— 与叶子同样
43
+ * 处理,因为点了没反应的控件比没有控件更糟。
44
+ */
45
+ onExpand?: (id: string) => void;
46
+ }
47
+ export declare function TreePanel({ nodes, rootId, selectedId, hoverId, query, onSelect, onShiftSelect, onHover, onMove, onExpand }: TreePanelProps): React.JSX.Element;