@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,126 @@
1
+ import { ELEMENT_CONTENT_MODE, type ApplyChangeResult, type CaptureElementResult, type CaptureSlice, type PrepareElementCaptureResult, type SetFixedHiddenResult, type ScrollToSelectorResult, type DomTreeNode, type GetDomTreeParams, type ElementCapabilities, type GetAncestorChainResult, type GetOuterHtmlResult, type ResolveSelectorResult, type ExperienceCode, type SetCodeResult, type MeasureDocumentParams, type MeasureDocumentResult, type SetStyleLayerResult, type ObserveDocumentSizeParams, type ObserveDocumentSizeResult, type QueryElementsParams, type QueryElementsResult, type ElementsAtPointsParams, type ElementsAtPointsResult, type ConfigureParams, type ConfigureResult, type ResolveHeatmapSelectorResult, type ElementsInBandResult, type GetElementImagesResult } from '@ptengine/inline-core/protocol';
2
+ import type { InlineChange } from '@ptengine/inline-core/change-format';
3
+ /**
4
+ * 六条 frame 命令(含 D9 的 setCode)的产品侧封装。全部走既有的 `sendCanvasCommand`(`chrome.runtime.sendMessage`
5
+ * 回调链)——SW 会 await agent 的回复后再 resolve 这一次调用,所以这里天然是 Promise
6
+ * 语义,**不需要自己发明 requestId 或超时**(D2)。失败原样抛出,message 就是
7
+ * FRAME_ROUTE_ERRORS 里的错误码,UI 直接展示即可。
8
+ */
9
+ export declare function setPickMode(sessionId: string, enabled: boolean): Promise<{
10
+ ok: true;
11
+ }>;
12
+ export declare function getDomTree(sessionId: string, opts?: Omit<GetDomTreeParams, 'command' | 'sessionId'>): Promise<DomTreeNode>;
13
+ /**
14
+ * 设置页面级自定义 CSS / JS(P10 → D9,§4.4)。**全量替换**语义,不是增量合并——传入的
15
+ * `codeList` 就是画布上要生效的完整集合,agent 侧会先清掉上一次 `setCode` 注入的
16
+ * `<style>` 再按顺序重新注入。因此天然幂等,不需要 operationId 做去重。
17
+ *
18
+ * 画布内行为不对称,这是硬约束不是策略选择:`css` 条目实时生效(agent 注入 `<style>`
19
+ * 到目标文档 `<head>`);`javascript` 条目**不执行**,只被 agent 存起来——用户写的 JS
20
+ * 依赖 `ptKit`(SDK 运行时 API),画布场景下客户页很可能没装 SDK,`ptKit` 不存在,
21
+ * 执行了也只会报错。回执 `cssInjectedCount` / `jsStoredCount` 供调用方直接展示,
22
+ * 不用自己在前端重新数一遍 `codeList`。
23
+ */
24
+ export declare function setCode(sessionId: string, codeList: ExperienceCode[]): Promise<SetCodeResult>;
25
+ /** 度量文档。`excludeLayers` 让 agent 在同一任务内摘层-回流-测量-复原(不闪),见契约注释。 */
26
+ export declare function measureDocument(sessionId: string, opts?: Omit<MeasureDocumentParams, 'command' | 'sessionId'>): Promise<MeasureDocumentResult>;
27
+ /** 按名字分层注入 CSS;`css` 为空串 = 删除该层。 */
28
+ export declare function setStyleLayer(sessionId: string, layer: string, css: string, order?: number): Promise<SetStyleLayerResult>;
29
+ /** 打开 / 关闭文档尺寸观察;变化经 `documentSize` 事件到 `useCanvasStream` 的 `onDocumentSize`。 */
30
+ export declare function observeDocumentSize(sessionId: string, opts: Omit<ObserveDocumentSizeParams, 'command' | 'sessionId'>): Promise<ObserveDocumentSizeResult>;
31
+ /** 批量 selector 查询,agent 只回原始事实(按 `fields`)。 */
32
+ export declare function queryElements(sessionId: string, selectors: string[], opts?: Omit<QueryElementsParams, 'command' | 'sessionId' | 'selectors'>): Promise<QueryElementsResult>;
33
+ /** 按文档坐标取元素及祖先;采样网格与打分由调用方决定。 */
34
+ export declare function elementsAtPoints(sessionId: string, points: Array<{
35
+ x: number;
36
+ y: number;
37
+ }>, opts?: Omit<ElementsAtPointsParams, 'command' | 'sessionId' | 'points'>): Promise<ElementsAtPointsResult>;
38
+ /** 会话级可调项(只覆盖传入的键)。 */
39
+ export declare function configureCanvas(sessionId: string, options: Omit<ConfigureParams, 'command' | 'sessionId'>): Promise<ConfigureResult>;
40
+ export { ELEMENT_CONTENT_MODE };
41
+ export declare function getElementCapabilities(sessionId: string, selector?: string): Promise<ElementCapabilities>;
42
+ export declare function getAncestorChain(sessionId: string, selector?: string): Promise<GetAncestorChainResult>;
43
+ /**
44
+ * ⚠️ **未命中不是错误**:agent 会 resolve 一个 `{ matched: false }`,而不是 reject
45
+ * (DEC-16 的正常分支 —— 客户改版后老 selector 失效,选中态恢复不了就清空,不该弹错)。
46
+ */
47
+ export declare function resolveCanvasSelector(sessionId: string, selector: string): Promise<ResolveSelectorResult>;
48
+ /** 空串 = 清空选中。 */
49
+ export declare function setSelection(sessionId: string, selector: string): Promise<{
50
+ ok: true;
51
+ }>;
52
+ /**
53
+ * DEC-23:悬浮路径选择器节点时预览高亮。
54
+ * ⚠️ **空串必须发**(鼠标移出路径条时),否则画布里高亮残留。
55
+ */
56
+ export declare function setHoverTarget(sessionId: string, selector: string): Promise<{
57
+ ok: true;
58
+ }>;
59
+ /**
60
+ * 「定位」:把 selector 指向的元素滚进视野。
61
+ *
62
+ * ⚠️ **不能用 `scrollFrame` 代替** —— 那条吃文档 y,而定位时产品页压根不知道目标的
63
+ * 文档坐标(几何流只推**当前选中**元素的矩形,且是异步的,跟这次点击竞态)。
64
+ * 算坐标只有 agent 侧摸得到 DOM。
65
+ */
66
+ export declare function scrollToSelector(sessionId: string, selector: string): Promise<ScrollToSelectorResult>;
67
+ /** 把一条**明文形态**的 change 应用到画布(DEC-22:转译只发生在落盘那一步)。 */
68
+ export declare function applyChange(sessionId: string, changeId: string, change: InlineChange): Promise<ApplyChangeResult>;
69
+ export declare function revertChange(sessionId: string, changeId: string): Promise<{
70
+ ok: true;
71
+ }>;
72
+ /** `</>` 的 seed:**outerHTML**(不是 innerHTML),且已剥 script/noscript。 */
73
+ export declare function getOuterHtml(sessionId: string, selector?: string): Promise<GetOuterHtmlResult>;
74
+ /**
75
+ * 区块里真实图片的 URL:`<img src>` + **计算样式**里的 `background-image`(v10)。
76
+ * outerHTML 里看不到 CSS 类设的背景图,首版给 agent 当 generate-image 参考图的 URL 只能从这里来。
77
+ * 老扩展不认识这条命令 → SW 回 `AGENT_STALE`,调用方按"没有图片行"降级。
78
+ */
79
+ export declare function getElementImages(sessionId: string, selector?: string, limit?: number): Promise<GetElementImagesResult>;
80
+ /**
81
+ * 页面内实时改文字(问题 3)。
82
+ *
83
+ * 结束**不走返回值**:用户可能按 Esc、可能点到别处,两条路都不是「产品侧发了 disable
84
+ * 命令」。结果统一由 agent 的 `textEditCommit` 事件回报。
85
+ */
86
+ export declare function setTextEditing(sessionId: string, enabled: boolean, selector?: string): Promise<{
87
+ ok: true;
88
+ }>;
89
+ /** 截图前滚动进视口并测量(**视口**坐标,不是文档坐标)。 */
90
+ export declare function prepareElementCapture(sessionId: string, selector?: string): Promise<PrepareElementCaptureResult>;
91
+ /**
92
+ * 抓屏 + 裁剪。**由 SW 自处理**(不转发给 agent),`rect` 是屏幕物理像素坐标。
93
+ */
94
+ export declare function captureElement(sessionId: string, rect: {
95
+ x: number;
96
+ y: number;
97
+ width: number;
98
+ height: number;
99
+ },
100
+ /** 元素比可视区高时的分屏方案;省略/单屏则直接截一次。 */
101
+ slices?: CaptureSlice[]): Promise<CaptureElementResult>;
102
+ /**
103
+ * 截图期间临时藏起 fixed/sticky。
104
+ *
105
+ * ⚠️ agent 侧只改 `visibility` 不改 `position`(改 position 会让元素重回文档流,
106
+ * 下面内容整体上移,图与真实页面对不上)。
107
+ */
108
+ export declare function setFixedHidden(sessionId: string, hidden: boolean): Promise<SetFixedHiddenResult>;
109
+ /**
110
+ * 热图 / SDK 的 jQuery 方言 selector(`body>#wrapper>div:eq(0)`,可能带 `v2>`)→ 在活页面上
111
+ * 试命中。**一次带全部**:一页十几个区块、每块若干条,逐条往返太慢。
112
+ *
113
+ * ⚠️ 未命中 / 多命中不是错误(客户改版后老 selector 失效是常态),看 `results[].matchCount`;
114
+ * 只有 `matchCount === 1` 的才带 `kernelSelector`(内核方言,画布闸门唯一认的那种)。
115
+ */
116
+ export declare function resolveHeatmapSelector(sessionId: string, selectors: string[]): Promise<ResolveHeatmapSelectorResult>;
117
+ /**
118
+ * 几何映射:文档 y 区间 → 最贴合的容器候选(按 1-D IoU 降序,最多 3 条)。
119
+ * agent 会滚动取样再恢复 scrollY;`minIou` 不传时 agent 用默认 0.3(这里不补默认,
120
+ * 免得两边各写一份数字)。
121
+ */
122
+ export declare function elementsInBand(sessionId: string, band: {
123
+ yStart: number;
124
+ yEnd: number;
125
+ minIou?: number;
126
+ }): Promise<ElementsInBandResult>;
@@ -0,0 +1,146 @@
1
+ import { ELEMENT_CONTENT_MODE } from '@ptengine/inline-core/protocol';
2
+ import { sendCanvasCommand } from './useCanvasBridge.js';
3
+ export function setPickMode(sessionId, enabled) {
4
+ return sendCanvasCommand({ command: 'setPickMode', sessionId, enabled });
5
+ }
6
+ export function getDomTree(sessionId, opts = {}) {
7
+ return sendCanvasCommand({
8
+ command: 'getDomTree',
9
+ sessionId,
10
+ ...opts
11
+ });
12
+ }
13
+ export function setCode(sessionId, codeList) {
14
+ return sendCanvasCommand({ command: 'setCode', sessionId, codeList });
15
+ }
16
+ export function measureDocument(sessionId, opts = {}) {
17
+ return sendCanvasCommand({ command: 'measureDocument', sessionId, ...opts });
18
+ }
19
+ export function setStyleLayer(sessionId, layer, css, order) {
20
+ return sendCanvasCommand({
21
+ command: 'setStyleLayer',
22
+ sessionId,
23
+ layer,
24
+ css,
25
+ ...(order !== undefined ? { order } : {})
26
+ });
27
+ }
28
+ export function observeDocumentSize(sessionId, opts) {
29
+ return sendCanvasCommand({ command: 'observeDocumentSize', sessionId, ...opts });
30
+ }
31
+ export function queryElements(sessionId, selectors, opts = {}) {
32
+ return sendCanvasCommand({ command: 'queryElements', sessionId, selectors, ...opts });
33
+ }
34
+ export function elementsAtPoints(sessionId, points, opts = {}) {
35
+ return sendCanvasCommand({ command: 'elementsAtPoints', sessionId, points, ...opts });
36
+ }
37
+ export function configureCanvas(sessionId, options) {
38
+ return sendCanvasCommand({ command: 'configure', sessionId, ...options });
39
+ }
40
+ export { ELEMENT_CONTENT_MODE };
41
+ export function getElementCapabilities(sessionId, selector) {
42
+ return sendCanvasCommand({
43
+ command: 'getElementCapabilities',
44
+ sessionId,
45
+ selector
46
+ });
47
+ }
48
+ export function getAncestorChain(sessionId, selector) {
49
+ return sendCanvasCommand({
50
+ command: 'getAncestorChain',
51
+ sessionId,
52
+ selector
53
+ });
54
+ }
55
+ export function resolveCanvasSelector(sessionId, selector) {
56
+ return sendCanvasCommand({
57
+ command: 'resolveSelector',
58
+ sessionId,
59
+ selector
60
+ });
61
+ }
62
+ export function setSelection(sessionId, selector) {
63
+ return sendCanvasCommand({ command: 'setSelection', sessionId, selector });
64
+ }
65
+ export function setHoverTarget(sessionId, selector) {
66
+ return sendCanvasCommand({ command: 'setHoverTarget', sessionId, selector });
67
+ }
68
+ export function scrollToSelector(sessionId, selector) {
69
+ return sendCanvasCommand({
70
+ command: 'scrollToSelector',
71
+ sessionId,
72
+ selector
73
+ });
74
+ }
75
+ export function applyChange(sessionId, changeId, change) {
76
+ return sendCanvasCommand({
77
+ command: 'applyChange',
78
+ sessionId,
79
+ changeId,
80
+ change
81
+ });
82
+ }
83
+ export function revertChange(sessionId, changeId) {
84
+ return sendCanvasCommand({ command: 'revertChange', sessionId, changeId });
85
+ }
86
+ export function getOuterHtml(sessionId, selector) {
87
+ return sendCanvasCommand({
88
+ command: 'getOuterHtml',
89
+ sessionId,
90
+ selector
91
+ });
92
+ }
93
+ export function getElementImages(sessionId, selector, limit) {
94
+ return sendCanvasCommand({
95
+ command: 'getElementImages',
96
+ sessionId,
97
+ selector,
98
+ limit
99
+ });
100
+ }
101
+ export function setTextEditing(sessionId, enabled, selector) {
102
+ return sendCanvasCommand({
103
+ command: 'setTextEditing',
104
+ sessionId,
105
+ enabled,
106
+ selector
107
+ });
108
+ }
109
+ export function prepareElementCapture(sessionId, selector) {
110
+ return sendCanvasCommand({
111
+ command: 'prepareElementCapture',
112
+ sessionId,
113
+ selector
114
+ });
115
+ }
116
+ export function captureElement(sessionId, rect, slices) {
117
+ return sendCanvasCommand({
118
+ command: 'captureElement',
119
+ sessionId,
120
+ rect,
121
+ slices
122
+ });
123
+ }
124
+ export function setFixedHidden(sessionId, hidden) {
125
+ return sendCanvasCommand({
126
+ command: 'setFixedHidden',
127
+ sessionId,
128
+ hidden
129
+ });
130
+ }
131
+ export function resolveHeatmapSelector(sessionId, selectors) {
132
+ return sendCanvasCommand({
133
+ command: 'resolveHeatmapSelector',
134
+ sessionId,
135
+ selectors
136
+ });
137
+ }
138
+ export function elementsInBand(sessionId, band) {
139
+ return sendCanvasCommand({
140
+ command: 'elementsInBand',
141
+ sessionId,
142
+ yStart: band.yStart,
143
+ yEnd: band.yEnd,
144
+ ...(band.minIou !== undefined ? { minIou: band.minIou } : {})
145
+ });
146
+ }
@@ -0,0 +1 @@
1
+ export declare function describeCanvasError(error: unknown): string;
@@ -0,0 +1,14 @@
1
+ import { FRAME_ROUTE_ERROR } from '@ptengine/inline-core/protocol';
2
+ const MESSAGES = {
3
+ [FRAME_ROUTE_ERROR.NO_SESSION]: '画布会话已失效,请重新打开编辑器',
4
+ [FRAME_ROUTE_ERROR.FRAME_NOT_READY]: '画布还没就绪,请稍候重试',
5
+ [FRAME_ROUTE_ERROR.FRAME_UNREACHABLE]: '画布已失联,请刷新页面重试',
6
+ [FRAME_ROUTE_ERROR.AGENT_ERROR]: '这一步在页面里执行失败了',
7
+ [FRAME_ROUTE_ERROR.AGENT_STALE]: '插件版本过旧:请重新加载扩展并刷新画布页',
8
+ EXTENSION_ID_MISSING: '插件 ID 未配置(构建配置问题,不是没装插件)',
9
+ NO_EXTENSION: '未检测到 Ptengine 插件,请先安装并启用'
10
+ };
11
+ export function describeCanvasError(error) {
12
+ const code = error instanceof Error ? error.message : String(error ?? '');
13
+ return MESSAGES[code] ?? code;
14
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 把「元素在客户页视口里的矩形」换算成「整屏截图位图里的矩形」。
3
+ *
4
+ * 三步,缺一不可:
5
+ * 1. **×zoom** —— iframe 上有 CSS `zoom`(把 1440 宽的客户页缩进面板)。
6
+ * 漏乘的症状是「元素越靠右下、框偏得越多」,看起来像随机偏移。
7
+ * 2. **+iframe 位置** —— `getBoundingClientRect()` 返回的**已经是缩放后**的视觉盒
8
+ * (CSS zoom 改的是布局盒尺寸),所以它**不要再乘一次 zoom**。
9
+ * 3. **×devicePixelRatio** —— `captureVisibleTab` 出的是物理像素位图。
10
+ * 漏乘的症状是在 Retina 上「只截到左上角四分之一」。
11
+ *
12
+ * 最后钳进 iframe 可视区:不钳的话 `drawImage` 会在位图边界外取样,得到透明边。
13
+ */
14
+ export interface Rect {
15
+ x: number;
16
+ y: number;
17
+ width: number;
18
+ height: number;
19
+ }
20
+ export interface CaptureGeometryInput {
21
+ /** 元素在客户页**视口**坐标系的矩形(agent 滚动之后测的)。 */
22
+ elementViewportRect: Rect;
23
+ /** iframe 元素在产品页视口坐标系的矩形(`getBoundingClientRect()`)。 */
24
+ iframeRect: Rect;
25
+ /** iframe 上的 CSS zoom。 */
26
+ zoom: number;
27
+ devicePixelRatio: number;
28
+ }
29
+ export type ScreenRect = Rect;
30
+ export declare function toScreenRect(input: CaptureGeometryInput): ScreenRect;
31
+ /**
32
+ * 元素在可视区里被切掉了吗。产品页据此回报 `truncated`(E-C5:截不全必须告知)。
33
+ * 用 1px 容差吸收取整误差 —— 不加容差的话,正常元素也会因为四舍五入被误报截断。
34
+ */
35
+ export declare function isTruncated(input: CaptureGeometryInput): boolean;
@@ -0,0 +1,25 @@
1
+ export function toScreenRect(input) {
2
+ const { elementViewportRect: el, iframeRect, zoom, devicePixelRatio: dpr } = input;
3
+ const left = iframeRect.x + el.x * zoom;
4
+ const top = iframeRect.y + el.y * zoom;
5
+ const right = left + el.width * zoom;
6
+ const bottom = top + el.height * zoom;
7
+ const clampedLeft = Math.max(left, iframeRect.x);
8
+ const clampedTop = Math.max(top, iframeRect.y);
9
+ const clampedRight = Math.min(right, iframeRect.x + iframeRect.width);
10
+ const clampedBottom = Math.min(bottom, iframeRect.y + iframeRect.height);
11
+ const x = Math.round(clampedLeft * dpr);
12
+ const y = Math.round(clampedTop * dpr);
13
+ return {
14
+ x,
15
+ y,
16
+ width: Math.max(0, Math.round(clampedRight * dpr) - x),
17
+ height: Math.max(0, Math.round(clampedBottom * dpr) - y)
18
+ };
19
+ }
20
+ export function isTruncated(input) {
21
+ const { elementViewportRect: el, zoom, devicePixelRatio: dpr } = input;
22
+ const full = { width: el.width * zoom * dpr, height: el.height * zoom * dpr };
23
+ const actual = toScreenRect(input);
24
+ return actual.width < full.width - 1 || actual.height < full.height - 1;
25
+ }
@@ -0,0 +1,48 @@
1
+ import { type Rect } from './captureGeometry.js';
2
+ /**
3
+ * 元素截图的编排。**顺序就是它的全部价值**:
4
+ * 先滚动测量 → 再隐藏浮层 → 再抓屏 → 最后恢复浮层。
5
+ *
6
+ * 隐藏放在抓屏之后 = 高亮框和工具条被截进客户页的图里(E-C4)。
7
+ * 恢复没放在 finally = 抓屏一失败,编辑器从此没有高亮框和工具条,只能刷新页面。
8
+ *
9
+ * 依赖注入而不是直接调命令:这样这段顺序逻辑能在没有插件、没有浏览器的环境里测。
10
+ */
11
+ export interface CaptureOutcome {
12
+ dataUrl: string;
13
+ width: number;
14
+ height: number;
15
+ /** 元素被可视区切掉了(E-C5:截不全必须告知)。 */
16
+ truncated: boolean;
17
+ /** 实际采样倍率 = zoom × devicePixelRatio。低于 1 表示比 1:1 糊。 */
18
+ scale: number;
19
+ }
20
+ export interface RunCaptureDeps {
21
+ prepare: (selector: string) => Promise<{
22
+ viewportRect: Rect;
23
+ viewportWidth: number;
24
+ viewportHeight: number;
25
+ docRect: Rect;
26
+ documentHeight: number;
27
+ scrollY: number;
28
+ }>;
29
+ capture: (rect: Rect, slices?: Array<{
30
+ scrollTo: number;
31
+ sourceTop: number;
32
+ height: number;
33
+ destTop: number;
34
+ }>) => Promise<{
35
+ dataUrl: string;
36
+ width: number;
37
+ height: number;
38
+ }>;
39
+ /** 隐藏浮层并**等到画面真的更新**(两帧)。 */
40
+ hideOverlays: () => Promise<void>;
41
+ showOverlays: () => void;
42
+ readViewport: () => {
43
+ iframeRect: Rect;
44
+ zoom: number;
45
+ devicePixelRatio: number;
46
+ };
47
+ }
48
+ export declare function runCapture(selector: string, deps: RunCaptureDeps): Promise<CaptureOutcome>;
@@ -0,0 +1,43 @@
1
+ import { isTruncated, toScreenRect } from './captureGeometry.js';
2
+ import { planSlices } from './captureSlices.js';
3
+ export async function runCapture(selector, deps) {
4
+ const prepared = await deps.prepare(selector);
5
+ const { iframeRect, zoom, devicePixelRatio } = deps.readViewport();
6
+ const geometry = {
7
+ elementViewportRect: prepared.viewportRect,
8
+ iframeRect,
9
+ zoom,
10
+ devicePixelRatio
11
+ };
12
+ const slices = planSlices({
13
+ elementTop: prepared.docRect.y,
14
+ elementHeight: prepared.docRect.height,
15
+ viewportHeight: prepared.viewportHeight,
16
+ documentHeight: prepared.documentHeight
17
+ });
18
+ const multi = slices.length > 1;
19
+ const scale = zoom * devicePixelRatio;
20
+ const screenRect = toScreenRect(geometry);
21
+ const wireSlices = multi
22
+ ? slices.map(s => ({
23
+ scrollTo: Math.round(s.scrollTo),
24
+ sourceTop: Math.round(iframeRect.y * devicePixelRatio + s.sourceTop * scale),
25
+ height: Math.round(s.height * scale),
26
+ destTop: Math.round(s.destTop * scale)
27
+ }))
28
+ : undefined;
29
+ try {
30
+ await deps.hideOverlays();
31
+ const shot = await deps.capture(screenRect, wireSlices);
32
+ return {
33
+ dataUrl: shot.dataUrl,
34
+ width: shot.width,
35
+ height: shot.height,
36
+ truncated: multi ? false : isTruncated(geometry),
37
+ scale
38
+ };
39
+ }
40
+ finally {
41
+ deps.showOverlays();
42
+ }
43
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * 把「一个比可视区高的元素」切成若干屏,供滚动 + 拼接。
3
+ *
4
+ * `captureVisibleTab` 只能抓到**屏幕上看得见的那一块**,所以高元素必须分几次滚过去各截
5
+ * 一次再拼。这里只算「滚到哪、这一屏取哪一段」,不碰任何浏览器 API。
6
+ *
7
+ * ⚠️ **最后一屏几乎必然滚不满。** 页面滚到底之后 `scrollY` 会被夹住,再要求它多滚一点
8
+ * 也不动 —— 这时那一屏的可见内容和上一屏是**重叠**的。直接把每屏整块往下拼会得到一张
9
+ * 中间重复一段的图,而且重复的量随页面剩余高度变化,看起来像「拼歪了」。
10
+ * 正确做法是:滚不动的那一屏只取**没被上一屏覆盖过**的那一段(见 `sourceTop`)。
11
+ */
12
+ export interface SlicePlan {
13
+ /** 要把 frame 滚到的文档 y。可能被页面夹住,实际值由 agent 回报。 */
14
+ scrollTo: number;
15
+ /** 这一屏里,从元素可见区域的第几像素开始取(用于处理滚不动导致的重叠)。 */
16
+ sourceTop: number;
17
+ /** 这一屏取多高。 */
18
+ height: number;
19
+ /** 拼到成品图的第几像素。 */
20
+ destTop: number;
21
+ }
22
+ export interface SliceInput {
23
+ /** 元素在**文档**坐标里的顶端。 */
24
+ elementTop: number;
25
+ /** 元素完整高度。 */
26
+ elementHeight: number;
27
+ /** frame 可视区高度。 */
28
+ viewportHeight: number;
29
+ /** 文档总高度 —— 用来算滚动能到的极限。 */
30
+ documentHeight: number;
31
+ }
32
+ /**
33
+ * 算出分屏方案。返回的每一项都是「滚到 scrollTo,然后从可视区的 sourceTop 取 height 高,
34
+ * 放到成品图的 destTop」。
35
+ */
36
+ export declare function planSlices(input: SliceInput): SlicePlan[];
@@ -0,0 +1,20 @@
1
+ export function planSlices(input) {
2
+ const { elementTop, elementHeight, viewportHeight, documentHeight } = input;
3
+ if (elementHeight <= 0 || viewportHeight <= 0)
4
+ return [];
5
+ const maxScroll = Math.max(0, documentHeight - viewportHeight);
6
+ const plans = [];
7
+ let covered = 0;
8
+ while (covered < elementHeight) {
9
+ const wantScroll = elementTop + covered;
10
+ const scrollTo = Math.min(wantScroll, maxScroll);
11
+ const sourceTop = wantScroll - scrollTo;
12
+ const available = viewportHeight - sourceTop;
13
+ if (available <= 0)
14
+ break;
15
+ const height = Math.min(available, elementHeight - covered);
16
+ plans.push({ scrollTo, sourceTop, height, destTop: covered });
17
+ covered += height;
18
+ }
19
+ return plans;
20
+ }
@@ -0,0 +1,13 @@
1
+ import type { ChangeRecord } from './useChangeList.js';
2
+ export interface ChangeListPanelProps {
3
+ list: ChangeRecord[];
4
+ onRevert: (changeId: string) => void;
5
+ lastError?: string | null;
6
+ /** 悬浮某条时在画布里高亮该元素。传空串表示清除。 */
7
+ onHover?: (selector: string) => void;
8
+ /** 点某条时选中该元素。 */
9
+ onSelect?: (selector: string) => void;
10
+ /** 全部撤销。省略则不显示该入口。 */
11
+ onRevertAll?: () => void;
12
+ }
13
+ export declare function ChangeListPanel({ list, onRevert, lastError, onHover, onSelect, onRevertAll }: ChangeListPanelProps): import("react").JSX.Element;
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from 'react';
3
+ import { toWireFormat } from '@ptengine/inline-core/change-format';
4
+ import { Code2, Crosshair, EyeOff, Image, Link2, Move, Plus, Trash2, Type, Undo2, Wand } from 'lucide-react';
5
+ import { Button } from '@ptengine/design-components';
6
+ import { summarizeChange } from './changeSummary.js';
7
+ import { classifySuppression } from './mergeForSave.js';
8
+ const ICONS = {
9
+ style: Wand,
10
+ delete: Trash2,
11
+ text: Type,
12
+ code: Code2,
13
+ image: Image,
14
+ link: Link2,
15
+ move: Move,
16
+ insert: Plus
17
+ };
18
+ export function ChangeListPanel({ list, onRevert, lastError, onHover, onSelect, onRevertAll }) {
19
+ const [view, setView] = useState('summary');
20
+ const entries = useMemo(() => list.map(r => ({ change: r.change, ancestors: r.ancestors })), [list]);
21
+ const json = useMemo(() => JSON.stringify(list.map(r => toWireFormat(r.change)), null, 2), [list]);
22
+ return (_jsxs("section", { "data-testid": "change-list-panel", className: "flex min-h-0 flex-1 flex-col", onMouseLeave: () => onHover?.(''), children: [_jsxs("header", { className: "flex h-10 shrink-0 items-center justify-between gap-2 border-b\n border-[#f1f2f3] pl-3 pr-2", children: [_jsxs("span", { className: "truncate text-[13px] font-medium text-[#1a1a1a]", children: ["\u6539\u52A8\u6E05\u5355", _jsx("span", { "data-testid": "change-list-count", className: "ml-1.5 text-xs font-normal tabular-nums text-[#8a8f98]", children: list.length })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [_jsx("div", { className: "flex items-center gap-px rounded-md bg-[#f3f4f4] p-0.5", children: [
23
+ ['summary', '摘要', undefined],
24
+ [
25
+ 'wire',
26
+ '数值',
27
+ '落盘形态,可与线上存量数据逐字段比对'
28
+ ]
29
+ ].map(([v, label, hint]) => (_jsx("button", { type: "button", "data-testid": `change-list-view-${v}`, "aria-pressed": view === v, title: hint, onClick: () => setView(v), className: `rounded px-2 py-[3px] text-[11px] transition-colors ${view === v
30
+ ? 'bg-white font-medium text-[rgba(16,28,31,0.9)] shadow-[0_1px_2px_rgba(16,28,31,0.08)]'
31
+ : 'text-[#707779] hover:text-[rgba(16,28,31,0.7)]'}`, children: label }, v))) }), onRevertAll && list.length > 0 && (_jsx("button", { type: "button", "data-testid": "change-list-revert-all", onClick: onRevertAll, className: "rounded px-2 py-1 text-xs text-[#52525b] transition-colors\n hover:bg-[rgba(16,28,31,0.06)] hover:text-[rgba(16,28,31,0.9)]", children: "\u5168\u90E8\u64A4\u9500" }))] })] }), lastError && (_jsx("p", { "data-testid": "change-list-error", className: "px-3 py-2 text-xs text-[#DE350B]", children: lastError })), list.length === 0 ? (_jsx("p", { "data-testid": "change-list-empty", className: "px-3 py-8 text-center text-xs text-[#8a8f98]", children: "\u8FD8\u6CA1\u6709\u6539\u52A8\u3002\u5728\u753B\u5E03\u91CC\u9009\u4E2D\u5143\u7D20\u5F00\u59CB\u7F16\u8F91\u3002" })) : view === 'summary' ? (_jsx("ul", { className: "min-h-0 flex-1 overflow-y-auto", children: list.map((record, index) => (_jsx(ChangeRow, { record: record, summary: summarizeChange(record.change), suppression: classifySuppression(entries, index), onRevert: onRevert, onHover: onHover, onSelect: onSelect }, record.changeId))) })) : (_jsx("pre", { "data-testid": "change-list-json", className: "min-h-0 flex-1 overflow-auto bg-[#FAFAFA] px-3 py-2 text-[11px]\n leading-4 text-[#344563]", children: json }))] }));
32
+ }
33
+ const SUPPRESSION_TEXT = {
34
+ 'ancestor-hidden': {
35
+ badge: '不会提交',
36
+ hint: '所在元素已被后面的「删除」改动隐藏。这条改动在画布上仍然生效,但保存时不会提交'
37
+ },
38
+ 'ancestor-replaced': {
39
+ badge: '不会提交',
40
+ hint: '所在元素的 HTML 已被后面的改动整体替换(新 HTML 里已含这条改动的结果)。保存时不会提交'
41
+ }
42
+ };
43
+ function ChangeRow({ record, summary, suppression, onRevert, onHover, onSelect }) {
44
+ const Icon = ICONS[summary.icon];
45
+ const text = suppression ? SUPPRESSION_TEXT[suppression.reason] : null;
46
+ return (_jsxs("li", { "data-testid": "change-list-item", "data-suppressed": suppression ? suppression.reason : undefined, className: "group flex items-center gap-2.5 border-b border-[#f1f2f3] px-3 py-2.5\n last:border-b-0 hover:bg-[rgba(16,28,31,0.04)]", onMouseEnter: () => onHover?.(summary.selector), children: [_jsx(Icon, { className: `size-4 shrink-0 ${suppression ? 'text-[#B3BAC5]' : 'text-[#6B778C]'}` }), _jsxs("button", { type: "button", "data-testid": "change-list-locate", onClick: () => onSelect?.(summary.selector), className: "flex min-w-0 flex-1 flex-col items-start gap-0.5 text-left", title: summary.selector, children: [_jsxs("span", { className: `flex items-center gap-1.5 text-[13px] font-medium ${suppression ? 'text-[#8993A4]' : 'text-[#1a1a1a]'}`, children: [summary.action, onSelect && (_jsx(Crosshair, { className: "size-3 text-[#8993A4] opacity-0 group-hover:opacity-100" }))] }), text && (_jsxs("span", { "data-testid": "change-list-suppressed-badge", className: "flex items-center gap-1 rounded bg-[#FFF7E6] px-1.5 py-0.5\n text-[11px] leading-4 text-[#974F0C]", title: `${text.hint}(${suppression?.by})`, children: [_jsx(EyeOff, { className: "size-3 shrink-0" }), text.badge] })), summary.detail && (_jsx("span", { className: `w-full truncate font-mono text-[11px] ${suppression ? 'text-[#8993A4] line-through' : 'text-[#344563]'}`, children: summary.detail })), _jsx("span", { className: "w-full truncate font-mono text-[11px] text-[#8993A4]", children: summary.selector })] }), _jsx(Button, { variant: "ghost", size: "icon", "data-testid": `change-list-revert-${record.changeId}`, "aria-label": "\u64A4\u9500", onClick: () => onRevert(record.changeId), children: _jsx(Undo2, { className: "size-4" }) })] }));
47
+ }
@@ -0,0 +1,20 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * 改动清单的悬浮外壳 —— **点顶栏入口才出现**,锚在入口正下方、右对齐,浮在画布之上。
4
+ *
5
+ * 此前清单是右侧那条可拖宽的 `ResizablePanel`,默认常驻、占掉 22% 宽。它的问题不是
6
+ * 占地方,而是**大部分时间是空的**:用户进编辑器的头几分钟一条改动都没有,那一栏就是
7
+ * 一块白,而画布被挤窄到要横向滚。
8
+ *
9
+ * 开合规则(ESC 关 / 点外部关 / 点触发器不算点外部 / 点画布不关)全部归
10
+ * `useDismissOnOutside`,那里有每一条的理由。
11
+ *
12
+ * 关着时**整块不在 DOM 里**,也不挂任何监听 —— 不是 `display:none`。清单里每行都带
13
+ * hover→高亮画布元素的回调,留在 DOM 里会被别处的合成事件误触。
14
+ */
15
+ export interface ChangeListPopoverProps {
16
+ open: boolean;
17
+ onClose: () => void;
18
+ children: ReactNode;
19
+ }
20
+ export declare function ChangeListPopover({ open, onClose, children }: ChangeListPopoverProps): import("react").JSX.Element | null;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRef } from 'react';
3
+ import { useDismissOnOutside } from '../../chrome/useDismissOnOutside.js';
4
+ export function ChangeListPopover({ open, onClose, children }) {
5
+ const ref = useRef(null);
6
+ useDismissOnOutside(open, ref, onClose, 'changelist-trigger');
7
+ if (!open)
8
+ return null;
9
+ return (_jsx("div", { ref: ref, "data-testid": "changelist-popover", className: "absolute right-4 top-2 z-30 flex max-h-[calc(100%-1rem)] w-[360px]\n flex-col overflow-hidden rounded-lg border border-[#ececef] bg-white\n shadow-[0_8px_24px_rgba(16,28,31,0.14),0_2px_8px_rgba(16,28,31,0.08)]", children: children }));
10
+ }