@hankchen/canvas-editor 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +129 -63
- package/dist/index.js +282 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/session/editor.ts","../src/spatial/camera.ts","../src/spatial/hitTest.ts","../src/shared/layerPath.ts","../src/spatial/snap.ts","../src/spatial/create.ts","../src/spatial/resize.ts","../src/shared/expressionSchema.ts","../src/shared/expressionPath.ts","../src/shared/expressionScan.ts","../src/editing/alignCanvas.ts","../src/editing/clipboard.ts","../src/editing/findReplace.ts","../src/editing/layerPanel.ts","../src/editing/tableEditing.ts","../src/session/store.ts","../src/session/shortcuts.ts","../src/session/editorState.ts","../src/editing/fontCatalog.ts","../src/editing/upload.ts","../src/shared/specField.ts","../src/editing/styleClipboard.ts","../src/spatial/wheel.ts","../src/shared/expressionCandidates.ts","../src/shared/rowsPathCandidates.ts","../src/shared/textPoliciesMemo.ts","../src/shared/resourceStatus.ts","../src/index.ts"],"sourcesContent":["/**\n * EditorSession:headless 编辑会话——store 之上的渲染调度面(工单 05)。\n *\n * 职责(impl 研究文档 §2.1 的「双 canvas + 单 rAF 合帧 + 脏标」):\n * - store 订阅 → 分层脏标(doc/视口 → 双层;后续工单的选择/gizmo 只脏覆盖层)\n * → 注入的帧调度器合帧,每屏帧至多一次重绘;rAF 由绑定层注入,内核保持无 DOM。\n * - 内容层重绘驱动五原语模板(renderCanvas),视口变换喂给后端的可选能力口;\n * - 覆盖层(gizmo)绘制经 OverlayPainter 缝交给宿主:入参是与内容层一致的\n * 呈现视口(已对齐物理像素),由宿主自行施加变换后绘制;本层只保证\n * 「覆盖层脏不触发内容层」与两侧视口的一致性。\n * - 相机动作全部经 camera.ts 纯函数落到 store.ui.viewport(不进历史)。\n */\nimport {\n docPathToViewPath,\n lineHeightPx,\n renderCanvas,\n textOrigin,\n viewPathToDocPath,\n withTemplatePreview,\n type Canvas,\n type HorizontalAlign,\n type Padding,\n type RenderBackend,\n type TextLayoutPolicies,\n type TextLayer,\n type VerticalAlign,\n} from '@hankchen/canvas'\nimport type { PreviewViewportTransform, ViewportAwareBackend } from '@hankchen/canvas-browser-renderer'\nimport type { Draft } from 'immer'\nimport {\n DEFAULT_ZOOM_BOUNDS,\n PAN_TO_BOX_MARGIN_PX,\n fitRect,\n fitViewport,\n nextZoomByWheel,\n panBy,\n panToBox as panToBoxViewport,\n screenToScene,\n snapViewportToPhysicalPixels,\n zoomAtPoint,\n type Point,\n type Rect,\n type Size,\n type Viewport,\n type ZoomBounds,\n} from '../spatial/camera'\nimport type { LayerBox, Layer, LayerType, ImageLayer } from '@hankchen/canvas'\nimport { hitTest as hitTestAt } from '../spatial/hitTest'\nimport {\n resolveSnap,\n resolveSnapPoints,\n snapAxesFromBoxes,\n snapThresholdScene,\n visibleRootBoxes,\n type Guide,\n type GuideOrientation,\n type SnapAxis,\n} from '../spatial/snap'\nimport { CREATE_DEAD_ZONE_SCREEN_PX, createRubberBandRect } from '../spatial/create'\nimport {\n handleResizesHeight,\n handleResizesWidth,\n resizeBox,\n resizeHandleAt as resizeHandleHit,\n resizeHandlesAt as resizeHandlesForPath,\n resizeSnapPoints,\n resizableAxesAt,\n RESIZE_HANDLES,\n type ResizeHandle,\n} from '../spatial/resize'\nimport { anchorOffset, layerHeight, layerWidth } from '@hankchen/canvas'\nimport {\n isTemplateSubtreePath,\n isLockedPath,\n layerBoxByPath,\n isRootLayerPath,\n pathStartsWith,\n pathsEqual,\n remapPathAfterSplice,\n resolveLayer,\n rootLayerOf,\n selectionParentPath,\n type LayerPath,\n} from '../shared/layerPath'\nimport { normalizeExpressionSchemaSource } from '../shared/expressionSchema'\nimport { scanExpressionFragments } from '../shared/expressionScan'\nimport {\n ALIGN_CORNER_MARGIN_PX,\n alignToCanvasTarget,\n type AlignToCanvasMode,\n type AlignToCanvasOptions,\n} from '../editing/alignCanvas'\nimport {\n PASTE_OFFSET_PX,\n canCopyLayerAt as canCopyLayerAtPath,\n cloneLayerSubtree,\n pastePosition,\n prepareRootPaste,\n type ClipboardEntry,\n} from '../editing/clipboard'\nimport {\n replaceHitsInDraft,\n scanTextMatches,\n type FindTextHit,\n} from '../editing/findReplace'\nimport {\n addRootLayerInDraft,\n createDefaultLayer,\n createTemplateTable,\n deleteLayerInDraft,\n insertRootLayerAdjacentInDraft,\n insertRootLayerInDraft,\n moveRootLayerInDraft,\n moveTableRowInDraft,\n pickNextTextStylePatch,\n TEXT_PRESETS,\n type DeletedLayerRef,\n type NextTextStyle,\n type TextPresetId,\n} from '../editing/layerPanel'\nimport {\n addTableCellInDraft,\n addTableRowInDraft,\n addTemplateCellInDraft,\n canonicalizeTableSyncInDraft,\n convertTableToRowsInDraft,\n convertTableToTemplateInDraft,\n moveTableCellInDraft,\n moveTableCellToRowInDraft,\n moveTableRowToTableInDraft,\n setCellAutoHeightInDraft,\n type MovedSubtreeRef,\n} from '../editing/tableEditing'\nimport { EditorStore, type DragGesture, type EditorChange, type TransactOptions } from './store'\nimport { ARM_CREATE_LAYER_TYPES, type EditorShortcutAction } from './shortcuts'\nimport {\n parseCanvasEditorState,\n type CanvasEditorState,\n type EditorPrefsState,\n} from './editorState'\nimport { FontCatalog, type FontCatalogEntry } from '../editing/fontCatalog'\nimport {\n UploadHandlerMissingError,\n uploadDisplayName,\n type UploadFile,\n type UploadHandler,\n type UploadImagePlacement,\n} from '../editing/upload'\nimport {\n STYLE_FIELD_KEYS_BY_TYPE,\n applyStyleFieldsInDraft,\n captureStyleSnapshot,\n type StyleSnapshot,\n} from '../editing/styleClipboard'\nimport { writeSpecField } from '../shared/specField'\n\n/** transact 回调向外传值的容器:TS 会把闭包内赋值的 let 窄化回初值类型,盒属性访问不受影响 */\ntype TxOut<T> = { v: T }\n\n/** 帧调度器:返回取消函数(绑定层注入 requestAnimationFrame 的包装) */\nexport type FrameScheduler = (callback: () => void) => () => void\n\n/** 覆盖层绘制缝的入参:viewport 已对齐物理像素(与内容层同一呈现视口) */\nexport interface OverlayPaintArgs {\n viewport: Viewport\n dpr: number\n doc: Canvas | null\n}\n\nexport type OverlayPainter = (args: OverlayPaintArgs) => void\n\nexport type InvalidateTarget = 'content' | 'overlay' | 'both'\n\n/**\n * 文本编辑 overlay 的布局描述(textEditLayout 的产物):场景坐标系的盒几何与\n * 排版字段。CSS 换算(transform 缩放/字体族解析/行盒模型)归绑定层,内核保持\n * 无 CSS 语义。\n */\nexport interface TextEditLayout {\n /** 编辑中图层的绝对盒(场景坐标;textarea 覆盖整盒,文字按 padding 内缩) */\n box: LayerBox\n /** 进入编辑时的文档文本(textarea 初值) */\n text: string\n /** 原始字体引用(空串/纯数字 = 内置默认字体;URL 由绑定层解析注册族名) */\n font: string\n fontColor: string\n /** 场景字号(textarea 字号固定取它,缩放交给 CSS transform——字号视觉恒定) */\n fontSize: number\n /** 行高像素 = ceil(fontSize × lineHeight),与布局层同式 */\n lineHeightPx: number\n padding: Padding\n horizontalAlign: HorizontalAlign\n verticalAlign: VerticalAlign\n /**\n * 首行绘制锚点相对内容盒顶部的偏移(textOrigin.y,与 canvas 绘制同一数值)。\n * 锚点语义随 verticalAlign:top = 字形盒顶、center = 字形盒心、bottom = 字形盒底\n * (渲染端以字体 metrics 消化基线差,绑定层的 CSS 行盒换算须按同一语义折算,\n * 不能直接当 padding-top 用——否则 center/bottom 会差半行/一行)。\n */\n verticalAnchorY: number\n /** autowrap = textarea pre-wrap 软换行,否则 pre 单行(断行允许与预览不同,决策 A) */\n autowrap: boolean\n /**\n * 内容类型会话模式(canvas-web-expression-editing 工单 01):true = 表达式——\n * 绑定层 pill 激活态与补全 enabled 的消费面。取会话标志(编辑中 pill 切换\n * 实时跟随,不受层标记态影响——面板在会话窗口期显示的是提交前旧状态);路径\n * 无活动会话时按层标记态回落(进入编辑即锚定同值)。\n */\n expressionMode: boolean\n}\n\n/** 拖动事务的合并键:一次拖动的全部 pointermove 并成一步历史 */\nconst DRAG_MERGE_KEY = 'drag'\n\n/** 缩放事务的合并键(工单 07):一次八柄手势的全部位移并成一步历史 */\nconst RESIZE_MERGE_KEY = 'resize'\n\n/**\n * Alt+拖快速复制的死区(alt-drag-paste 工单 01,spec 决策 1):屏幕 css 像素、\n * 缩放无关(场景阈值 = 阈值 / zoom,SNAP_THRESHOLD_SCREEN_PX 同风格)——首移\n * 越过死区才插入副本,死区内抬手退化为普通点选。\n */\nexport const ALT_DRAG_DEAD_ZONE_SCREEN_PX = 4\n\n/** 微调步长(kbd-nav 工单 02):基础步 1 场景 px,Shift 大步 10 px 固定 */\nconst NUDGE_STEP_PX = 1\nconst NUDGE_COARSE_STEP_PX = 10\n\n/** 微调事务的合并键:含选中层路径(换层即换步)、方向无关(连按跨方向同串) */\nconst nudgeMergeKey = (path: LayerPath): string => `nudge:${path.join(':')}`\n\n/** 查找游标钳位:派生命中列表内回落(末处/首处),空命中为 0(读侧收口) */\nfunction clampFindCursor(cursor: number, count: number): number {\n if (count <= 0) return 0\n return Math.min(Math.max(cursor, 0), count - 1)\n}\n\n/**\n * 串含 ≥1 语法有效闭合片段(canvas-web-expression-editing 工单 01,spec 决策 3):\n * `scanExpressionFragments` 切出 `kind: 'fragment'` 且 `error === null`——与求值器\n * `expression_syntax_error` 同码。未闭合 `{{` 是 open 片段(求值期字面直通)、\n * 空片段/路径空段带错误信号,均不算合法片段——表达式会话提交去向(标记写 vs\n * 字面回落)的判定原语,守住不变量「标记 ⟹ ≥1 合法片段」。\n */\n/**\n * updateSpec 字段路径 → 新层样式槽字段(text-defaults 工单 01):文本族五键\n * 映射(shape.lineHeight 是唯一两段键),未命中返回 null——captureNextTextStyle\n * 的键面白名单。\n */\nfunction captureFieldOfKey(key: readonly string[]): keyof NextTextStyle | null {\n const single = key.length === 1 ? key[0] : null\n if (single === 'font' || single === 'fontSize' || single === 'fontColor' || single === 'autowrap') return single\n if (key.length === 2 && key[0] === 'shape' && key[1] === 'lineHeight') return 'lineHeight'\n return null\n}\n\nfunction hasValidExpressionFragment(template: string): boolean {\n return scanExpressionFragments(template).parts.some(\n (part) => part.kind === 'fragment' && part.error === null,\n )\n}\n\n/**\n * 解码不变量重断言(updateSpec 与样式粘贴共用,写入口收口一处):autoHeight\n * 置位即声明高归零(decodeBase 对 auto 标志恒清零声明高,PHP setHeight('auto')\n * 同门;文本层由布局按行数×行高+padding 动态求高,其余类型按各自兜底)——\n * 否则保存再打开声明高被清,往返恒等破。格的 autoHeight 写入另有采纳/固化\n * 语义(updateSpec 拦截分流),不会走到这里。参数收只读联合:draft 结构上\n * 兼容只读类型,写入经显式转型(字段形态由领域类型把关)。\n */\nfunction renormalizeAutoHeightInDraft(layer: Layer): void {\n if ((layer as { shape?: { autoHeight?: boolean } }).shape?.autoHeight === true) {\n ;(layer as { shape: { height: number } }).shape.height = 0\n }\n}\n\nexport interface EditorSessionOptions {\n scheduleFrame: FrameScheduler\n /** 缩放范围(可配置,缺省 5%–800%) */\n zoomBounds?: ZoomBounds\n /** 适应画布时的屏幕像素内缩(缺省 0:画布边贴视口边) */\n fitMargin?: number\n /** 文本布局策略(断行/度量注入缝,缺省启发式对齐版) */\n textPolicies?: TextLayoutPolicies\n /**\n * 上传实现注入点(工单 13):本机资源 → 可物化引用。core 不内置任何实现;\n * 未注入时 canUpload 为 false、上传动作抛 UploadHandlerMissingError(宿主\n * 据此做降级提示),生产宿主接自己的存储,playground 以 data URL 兜底。\n */\n uploadHandler?: UploadHandler\n /** 内置字体清单(URL 列表可配置;缺省空清单,清单 ≠ 物化,加载走渲染端管线) */\n fontCatalog?: readonly FontCatalogEntry[]\n}\n\nexport class EditorSession {\n readonly store: EditorStore\n\n private readonly scheduleFrame: FrameScheduler\n private readonly zoomBounds: ZoomBounds\n private readonly fitMargin: number\n /**\n * 生效的文本布局策略(构造注入值,缺省 undefined = 启发式对齐版):公开只读,\n * 供导出等旁路面复用同一注入值——导出与编辑画布的断行/盒高才不分叉(工单 13)。\n */\n readonly textPolicies?: TextLayoutPolicies\n private readonly uploadHandler: UploadHandler | null\n private readonly fontCatalogValue: FontCatalog\n\n private backend: RenderBackend | null = null\n private overlayPainter: OverlayPainter | null = null\n private dpr = 1\n private surfaceSize: Size = { width: 0, height: 0 }\n private dirty = { content: false, overlay: false }\n private frameQueued = false\n private cancelFrame: (() => void) | null = null\n private readonly unsubscribeStore: () => void\n /**\n * 预览视图 memo(决策 2026-09):模板态表格实例化一行预览行的文档衍生画布,\n * 画布渲染/命中/导出消费,永不写回 graph(红线 3 延伸)。Canvas 不可变 +\n * 结构共享,doc 引用相等即视图仍有效(与 skipContent 的引用比较同纪律)。\n */\n private previewSource: Canvas | null = null\n private previewValue: Canvas | null = null\n /**\n * 会话级剪贴板(工单 14;alt-drag-paste 工单 02 收窄):复制源的子树深拷贝\n * 快照 + 复制时点绝对盒(粘贴落位基准,格内容的格内相对 position 据此换算\n * 画布绝对落位)+ 源路径(粘贴时紧邻插入的解析凭据)。不跨会话、不碰 OS\n * 剪贴板;openDocument 不清空——粘贴进另一份文档是合法用法(条目是与源树\n * 断开引用的普通对象;他文档 sourcePath 必失配 → 粘贴退化置顶)。\n */\n private clipboardEntry: ClipboardEntry | null = null\n /**\n * 样式剪贴板(canvas-web-style-paste 工单 01):源类型 + 适用面逐字段值快照。\n * 与图层剪贴板**分立互不覆盖**(⌘C 与 ⌥⌘C 并存);不碰 OS 剪贴板、不进历史\n * (剪贴板态不是文档态);openDocument 不清空——跨文档粘贴样式合法(图层\n * 剪贴板同门,快照是与源树断开引用的普通对象)。\n */\n private styleClipboardEntry: StyleSnapshot | null = null\n /** 参考线 id 自增序(会话内唯一即可,不持久化——参考线不写 graph) */\n private guideIdSeq = 0\n\n constructor(options: EditorSessionOptions) {\n this.scheduleFrame = options.scheduleFrame\n this.zoomBounds = options.zoomBounds ?? DEFAULT_ZOOM_BOUNDS\n this.fitMargin = options.fitMargin ?? 0\n this.textPolicies = options.textPolicies\n this.uploadHandler = options.uploadHandler ?? null\n this.fontCatalogValue = new FontCatalog(options.fontCatalog)\n this.store = new EditorStore()\n this.unsubscribeStore = this.store.subscribe((change) => this.onStoreChange(change))\n }\n\n // ---- 组装与呈现参数(DOM 侧由绑定层调用) ----\n\n attachContentBackend(backend: RenderBackend): void {\n this.backend = backend\n this.invalidate('content')\n }\n\n setOverlayPainter(painter: OverlayPainter | null): void {\n this.overlayPainter = painter\n this.invalidate('overlay')\n }\n\n /** 解绑绘制面(surface 卸载时);挂起帧 flush 空转,可重新挂载 */\n detachSurfaces(): void {\n this.backend = null\n this.overlayPainter = null\n }\n\n setDevicePixelRatio(dpr: number): void {\n if (dpr === this.dpr) return\n this.dpr = dpr\n // 物理缓冲语义变更:内容与覆盖层缓冲都需重设后重绘\n this.invalidate('both')\n }\n\n setSurfaceSize(width: number, height: number): void {\n if (width === this.surfaceSize.width && height === this.surfaceSize.height) return\n this.surfaceSize = { width, height }\n this.invalidate('both')\n }\n\n getSurfaceSize(): Readonly<Size> {\n return this.surfaceSize\n }\n\n // ---- 相机动作(视口 {x, y, zoom} 走 ui 分支,不进历史) ----\n\n /**\n * 打开/替换文档(转发 store;解码在宿主侧完成,内核只消费领域结构)。\n * state 传切片即按 restoreEditorState 同一管线原子应用(reset → apply 切片 →\n * prune)——「开了就恢复」,宿主切帧处不可能忘记重放;切片形状不合法整体拒\n * (画布照常以默认态打开),现网单参调用零破坏。\n */\n openDocument(canvas: Canvas, state?: CanvasEditorState): void {\n this.store.openDocument(canvas)\n if (state === undefined) return\n this.restoreEditorState(state)\n }\n\n /**\n * 预览视图(只读衍生):模板态表格在视图中获得一行预览行(文本格 = 表达式\n * 原文、标记图片/二维码格走占位盒),渲染/命中/导出据此呈现「一行输出效果」;\n * 文档本身不被改写。宿主(如导出)与内核内部共用同一 memo。\n */\n get previewCanvas(): Canvas | null {\n const doc = this.store.doc\n if (doc === null) return null\n if (doc !== this.previewSource) {\n this.previewValue = withTemplatePreview(doc, this.textPolicies)\n this.previewSource = doc\n }\n return this.previewValue\n }\n\n /** 平移:屏幕位移按 zoom 折算 */\n panBy(deltaScreenX: number, deltaScreenY: number): void {\n this.store.setViewport(panBy(this.store.ui.viewport, deltaScreenX, deltaScreenY))\n }\n\n /** 缩放至指定倍率,以屏幕点(视口坐标系的 css 像素)为锚 */\n zoomAt(screenX: number, screenY: number, nextZoom: number): void {\n this.store.setViewport(zoomAtPoint(this.store.ui.viewport, screenX, screenY, nextZoom, this.zoomBounds))\n }\n\n /** 滚轮/捏合:曲线见 camera.nextZoomByWheel,缩放以指针为中心 */\n zoomByWheel(screenX: number, screenY: number, deltaY: number): void {\n const viewport = this.store.ui.viewport\n const nextZoom = nextZoomByWheel(viewport.zoom, deltaY, this.zoomBounds)\n this.store.setViewport(zoomAtPoint(viewport, screenX, screenY, nextZoom, this.zoomBounds))\n }\n\n /**\n * 缩放复位 100%(⌘0,kbd-nav 工单 01):以视口中心为锚回到 1.0——中心场景点\n * 不动、平移不跳变(Sketch Actual Size 同构,playground zoomTo100 先例收编为\n * 内核面);已在 100% 时 zoomAtPoint 恒等短路。视口走 ui 分支不进历史。\n */\n resetZoom(): void {\n const { width, height } = this.surfaceSize\n this.zoomAt(width / 2, height / 2, 1)\n }\n\n /** 一键适应画布:整页可见、居中(表面尺寸未知时 no-op) */\n fitToSurface(): void {\n const doc = this.store.doc\n if (!doc || this.surfaceSize.width <= 0 || this.surfaceSize.height <= 0) return\n this.store.setViewport(fitViewport(doc, this.surfaceSize, this.zoomBounds, this.fitMargin))\n }\n\n /**\n * 路径处图层盒:模板子树路径经预览视图解析(auto 高在视图里合成,文档侧\n * 声明高为零),其余路径直接解析文档——非模板子树结构共享,两棵树同盒。\n * gizmo/命中/适应选区/文本编辑 overlay 共用,与绘制不漂移。\n */\n private boxByPath(path: LayerPath): LayerBox | null {\n const doc = this.store.doc\n if (!doc) return null\n if (isTemplateSubtreePath(path)) {\n const view = this.previewCanvas\n const viewPath = view ? docPathToViewPath(doc, path) : null\n return view && viewPath ? layerBoxByPath(view, viewPath as LayerPath, this.textPolicies) : null\n }\n return layerBoxByPath(doc, path, this.textPolicies)\n }\n\n /** 自适应选区视图:视口适配选中盒;无选择/退化盒回落适应画布 */\n fitToSelection(): void {\n const doc = this.store.doc\n if (!doc) return\n const selection = this.store.ui.selection\n const box = selection ? this.boxByPath(selection) : null\n if (!box || box.width <= 0 || box.height <= 0 || this.surfaceSize.width <= 0 || this.surfaceSize.height <= 0) {\n this.fitToSurface()\n return\n }\n this.store.setViewport(fitRect(box, this.surfaceSize, this.zoomBounds, this.fitMargin))\n }\n\n /**\n * 保 zoom 平移入视(canvas-web-find-replace 工单 02,spec 决策 6):查找导航\n * 的视口跟随——目标盒完全在视口内视口不动,出视才最小平移使整盒入视(入视\n * 小边距与盒大于视口对齐盒左上的边界语义在 camera.panToBox 纯函数内定死)。\n * 与 fitToSelection 分立不混用:那是变焦语义(fitRect),这是保 zoom 的跟随\n * 语义。盒几何由调用方解析(工单 03 命中导航经 layerBoxAt 同一解析缝,模板\n * 子树走预览视图);视口走 ui 分支不进历史,视内不动经恒等短路零通知;表面\n * 尺寸未知 no-op(fitToSurface 同门)。\n */\n panToBox(box: Rect): void {\n if (this.surfaceSize.width <= 0 || this.surfaceSize.height <= 0) return\n const viewport = this.store.ui.viewport\n const next = panToBoxViewport(viewport, box, this.surfaceSize, PAN_TO_BOX_MARGIN_PX)\n if (next !== viewport) this.store.setViewport(next)\n }\n\n // ---- 选择与拖动(工单 06):命中/选择/拖动几何全部经内核纯函数 ----\n\n /** 屏幕(css 像素)→ 场景:绑定层的唯一坐标入口(zoom 折算收口在此) */\n toScenePoint(screenX: number, screenY: number): Point {\n return screenToScene(this.store.ui.viewport, screenX, screenY)\n }\n\n /**\n * 场景点命中查询(不产生副作用):在预览视图上执行——模板态表格的预览行\n * 可命中,命中路径经视图→文档映射落回模板子树身份(选中/属性编辑写文档)。\n * 锁定集合(canvas-web-layer-lock 工单 01)随 ui 分支传入纯函数:锁定的根层\n * 整子树退出命中面,selectAt/hoverAt/右键/双击全经此一条缝自动生效。\n */\n hitTest(sceneX: number, sceneY: number): LayerPath | null {\n const view = this.previewCanvas\n if (!view) return null\n const hit = hitTestAt(view, sceneX, sceneY, this.textPolicies, {\n lockedPaths: this.store.ui.lockedPaths,\n })\n if (hit === null) return null\n const docPath = viewPathToDocPath(view, hit)\n return docPath ? (docPath as LayerPath) : null\n }\n\n /** 路径处图层的绝对盒(gizmo 选择框与后续面板共用;与命中同一布局策略) */\n layerBoxAt(path: LayerPath): LayerBox | null {\n return this.boxByPath(path)\n }\n\n /** 点选:命中即选中并返回路径,未中清空选择(画布与后续面板同源) */\n selectAt(sceneX: number, sceneY: number): LayerPath | null {\n const path = this.hitTest(sceneX, sceneY)\n this.store.setSelection(path)\n return path\n }\n\n /** 直接设置选择(面板/键盘入口) */\n setSelection(path: LayerPath | null): void {\n this.store.setSelection(path)\n }\n\n /** Escape 升级:沿归属链 cell→row→table 逐级取父,链尽清空选择 */\n escapeSelection(): void {\n const selection = this.store.ui.selection\n if (!selection) return\n this.store.setSelection(selectionParentPath(selection))\n }\n\n /**\n * 循环选层(kbd-nav 工单 03,CONTEXT「循环选层」词条 / spec 决策 4):Tab\n * 沿面板序朝更垫底方向移动选中(面板顶 = 视觉最上层 = 数组尾,故数组下标 −1),\n * ⇧Tab 反向(selectPrevLayer),端点 wrap 回绕;空选从视觉最上层(面板首行)\n * 开始(spec 决策 4 字面口径,⇧Tab 同一起点);子层选中以根祖先为基准取相邻\n * 根层(不下钻表格,循环只落根路径)。跳过面与命中面同门(hitTest 同款判定):\n * 隐藏根层整子树排除(visible=false 是排除语义非保护语义)、锁定根层整子树\n * 退出交互面(isLocked 谓词——Tab 是命中面的键盘投影,「画布挡误触」同门,\n * layer-lock 合入后随谓词自动生效);全部根层被跳过时空转返回 false。\n * 选中变更走 setSelection(ui 分支零历史步;值等短路使单根层 wrap 自身无害\n * ——不通知)。返回是否落到选中(executeShortcut 可用态口径)。\n */\n selectNextLayer(): boolean {\n return this.cycleRootSelection(1)\n }\n\n /** ⇧Tab 反向循环选层(语义见 selectNextLayer) */\n selectPrevLayer(): boolean {\n return this.cycleRootSelection(-1)\n }\n\n /** 循环选层的共同游走:direction = 1 朝数组头(垫底)、−1 朝数组尾(最上),模 count 回绕 */\n private cycleRootSelection(direction: 1 | -1): boolean {\n const doc = this.store.doc\n const count = doc?.layers.length ?? 0\n if (doc === null || count === 0) return false\n // 基准根下标:任意深度的选中取根祖先(路径第 2 段即根下标);悬空选中\n // (undo prune 窗口外的瞬态)视同空选按起点口径\n const selection = this.store.ui.selection\n const baseline =\n selection !== null && rootLayerOf(doc, selection) !== null ? (selection[1] as number) : null\n const startIndex = baseline === null ? count - 1 : baseline - direction\n for (let step = 0; step < count; step += 1) {\n const index = (((startIndex - direction * step) % count) + count) % count\n const path: LayerPath = ['layers', index]\n // 跳过面与命中面同门:visible=false 整子树排除;锁定经 isLocked 谓词收口\n if (doc.layers[index]!.visible === false || this.isLocked(path)) continue\n this.store.setSelection(path)\n return true\n }\n return false\n }\n\n /** 悬停命中(指针移动时调用;离场传 null 或用 setHovered) */\n hoverAt(sceneX: number, sceneY: number): void {\n this.store.setHovered(this.hitTest(sceneX, sceneY))\n }\n\n /** 设置悬停路径 */\n setHovered(path: LayerPath | null): void {\n this.store.setHovered(path)\n }\n\n /**\n * 开始拖动会话:记录目标路径、起点场景坐标、起始 position 偏移与起始盒\n * (吸附求位基准,ui 分支)。已在拖动中、路径或盒几何无法解析时返回 false;\n * 路径落在锁定子树同样拒绝(canvas-web-layer-lock 工单 01:拖动是变更类\n * 动作的 guard 入口——画布命中面已过滤锁定层,此守卫兜住其余手势来源,\n * 缩放手势接入 resize feature 时同用此门)。\n *\n * Alt+拖快速复制(alt-drag-paste 工单 01):options.copy 起手一次性判定——\n * 源不可复制(canCopyLayerAt 为 false:行/格等容器内结构)静默忽略 Alt 走\n * 普通拖动(spec 决策 1);置位后 copyMode 会话内定死,首移越过死区才插入\n * 副本(dragTo 消费,见 ALT_DRAG_DEAD_ZONE_SCREEN_PX)。\n */\n beginDrag(path: LayerPath, sceneX: number, sceneY: number, options: { copy?: boolean } = {}): boolean {\n if (this.store.ui.drag || this.store.ui.resize || this.store.ui.create) return false\n if (this.isLocked(path)) return false\n const doc = this.store.doc\n const layer = doc ? resolveLayer(doc, path) : null\n if (!doc || !layer) return false\n const box = this.boxByPath(path)\n if (!box) return false\n const copy = options.copy === true && canCopyLayerAtPath(doc, path)\n this.store.setDrag({\n path,\n startScene: { x: sceneX, y: sceneY },\n startPosition: { x: layer.position.x, y: layer.position.y },\n startBox: box,\n ...(copy ? { copy: true, copyPending: true } : {}),\n })\n return true\n }\n\n /**\n * 拖动进行中:position = 起点 + 场景位移,再按吸附修正(工单 01,ADR 0012)。\n * 吸附求位:暂定盒(startBox + 本步位移)九点对轴集合——其他可见根层盒缘与\n * 中心、画布水平/垂直中轴、参考线轴——屏幕 6px 阈值按当前缩放换算场景值,\n * 多轴取最近,至多一垂直 + 一水平;修正并入同一位置事务(同一 mergeKey,\n * 跟随拖动既有历史合并语义)。命中的吸附轴回写会话态供吸附线呈现(瞬时\n * 回显,endDrag 清空)。九锚点一视同仁——锚点偏移不动,只累加 x/y(x += dx/\n * zoom 的 zoom 折算已由 toScenePoint 收口);一次拖动的全部位移经同 mergeKey\n * 事务并成一步历史,属性面板读同一文档数据即联动。\n *\n * Alt+拖 copyMode(alt-drag-paste 工单 01):副本未插入前(copyPending)源层\n * 不动——未越死区零事务,首移越阈交 dragCopyFirstMove 单事务完成插入 + 位移,\n * 此后 path 已重指向副本、走普通拖动流程(吸附排除副本、源层供轴)。\n */\n dragTo(sceneX: number, sceneY: number): void {\n const drag = this.store.ui.drag\n if (!drag) return\n // 拖动中图层被结构编辑移除的边缘态:整个求位空转(不发事务、不发布命中轴)\n const doc = this.store.doc\n if (!doc || resolveLayer(doc, drag.path) === null) return\n if (drag.copy === true && drag.copyPending === true) {\n this.dragCopyFirstMove(drag, sceneX, sceneY)\n return\n }\n const dx = sceneX - drag.startScene.x\n const dy = sceneY - drag.startScene.y\n const resolution = resolveSnap(\n {\n x: drag.startBox.x + dx,\n y: drag.startBox.y + dy,\n width: drag.startBox.width,\n height: drag.startBox.height,\n },\n this.snapAxesForGesture(drag.path),\n snapThresholdScene(this.store.ui.viewport.zoom),\n )\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, drag.path)\n if (!layer) return\n // immer draft 原位写 position;类型层的 readonly 由 draft 语义解除\n const position = layer.position as { x: number; y: number }\n position.x = drag.startPosition.x + dx + resolution.dx\n position.y = drag.startPosition.y + dy + resolution.dy\n }, { mergeKey: DRAG_MERGE_KEY })\n this.store.setSnapAxes(resolution.axes)\n }\n\n /**\n * copyMode 首移(alt-drag-paste 工单 01,spec 决策 1/3):先过死区——位移按\n * 屏幕 4px 阈值折算(缩放无关),未越阈零事务、源层不动(Alt+点击退化);\n * 越阈则单事务完成「插入副本(紧邻源层)+ 副本自源盒起步位移」:副本 = 源\n * 子树深拷贝,落位按根层语义反解(pastePosition——根层源退化为 position +\n * 位移、格内容源把格内相对 position 换算画布绝对落位),与吸附修正一并写入,\n * mergeKey 沿用 DRAG_MERGE_KEY,后续 move 同键合并——undo 一次副本整体消失。\n * 吸附求位在副本入库前:吸附源 = 全部可见根层(副本尚不存在、源层天然在列\n * ——重叠起步,spec 决策 4);插入后手势重指向副本(startPosition 换基准为\n * 副本起步位 = 源绝对盒的根层语义反解,startBox 即副本起步盒不变),副本自动\n * 选中,命中轴照常回显。\n */\n private dragCopyFirstMove(drag: DragGesture, sceneX: number, sceneY: number): void {\n const doc = this.store.doc\n if (!doc) return\n const source = resolveLayer(doc, drag.path)\n if (!source) return\n const dx = sceneX - drag.startScene.x\n const dy = sceneY - drag.startScene.y\n if (Math.hypot(dx, dy) <= ALT_DRAG_DEAD_ZONE_SCREEN_PX / this.store.ui.viewport.zoom) return\n const resolution = resolveSnap(\n {\n x: drag.startBox.x + dx,\n y: drag.startBox.y + dy,\n width: drag.startBox.width,\n height: drag.startBox.height,\n },\n this.snapAxesForGesture(null),\n snapThresholdScene(this.store.ui.viewport.zoom),\n )\n const copyIndex: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n const sourceInDraft = resolveLayer(draft, drag.path)\n if (!sourceInDraft) return\n const prepared = prepareRootPaste(\n sourceInDraft,\n drag.startBox,\n doc.width,\n doc.height,\n dx + resolution.dx,\n dy + resolution.dy,\n )\n copyIndex.v = insertRootLayerAdjacentInDraft(draft, prepared as Draft<Layer>, drag.path) ?? -1\n }, { mergeKey: DRAG_MERGE_KEY })\n if (copyIndex.v < 0) return\n const copyPath: LayerPath = ['layers', copyIndex.v]\n this.store.setDrag({\n ...drag,\n path: copyPath,\n startPosition: pastePosition(drag.startBox, source.position.anchor, doc.width, doc.height, 0, 0),\n copyPending: false,\n })\n this.store.setSelection(copyPath)\n this.store.setSnapAxes(resolution.axes)\n }\n\n /** 结束拖动:闭合合并事务(下一步历史定格),会话态与命中轴回显清空 */\n endDrag(): void {\n if (!this.store.ui.drag) return\n this.store.setDrag(null)\n this.store.setSnapAxes([])\n this.store.closeMerge(DRAG_MERGE_KEY)\n }\n\n /**\n * 手势可用的吸附轴集合(dragTo/resizeTo 每步重算,几何与其余消费同源):\n * 可见根层盒(hitTest 同款 visible 过滤)排除手势目标层自身根 + 画布中轴 +\n * 当前参考线。自身根排除对缩放同样正确——被缩放层的自缘不供轴(对缘与移动\n * 缘都在自身盒上,供轴只会命中自己)。gesturePath 传 null = 排除面为空\n * (copyMode 首移:副本未入库,源层供轴,alt-drag-paste 工单 01)。\n */\n private snapAxesForGesture(gesturePath: LayerPath | null): readonly SnapAxis[] {\n const doc = this.store.doc\n if (!doc) return []\n const excludeRootIndex = gesturePath === null ? null : gesturePath[1]\n if (excludeRootIndex !== null && typeof excludeRootIndex !== 'number') return []\n return snapAxesFromBoxes(\n doc.width,\n doc.height,\n visibleRootBoxes(doc, excludeRootIndex, this.textPolicies),\n this.store.ui.guides,\n )\n }\n\n /** 当次手势命中的吸附轴(只读查询,吸附线呈现消费);非手势态/未命中为空 */\n listSnapAxes(): readonly SnapAxis[] {\n return this.store.ui.snapAxes\n }\n\n // ---- 八柄缩放(工单 07):几何纯函数在 spatial/resize,写回/吸附/采纳在此收口 ----\n\n /**\n * 路径处图层当前可用的八柄集合(gizmo 柄渲染与手势起点同缝):角色权威\n * 过滤(行宽=表宽、格内容尺寸强同步面不可经柄改,语义见\n * spatial/resize.resizableAxesAt)+ 锁定子树不出柄(锁定层只画选中框,\n * gizmo 同门——柄是变换入口,锁定=可定位不可变换)。\n */\n resizeHandlesAt(path: LayerPath | null): readonly ResizeHandle[] {\n const doc = this.store.doc\n if (!doc || path === null || this.isLocked(path)) return []\n return resizeHandlesForPath(doc, path)\n }\n\n /**\n * 场景点命中选中层的可用柄(hitTest/hoverAt 同门的意图级查询,画布接线\n * 与 gizmo 消费):命中半径按屏幕 6px 常量折算(RESIZE_HANDLE_HIT_PX,随\n * zoom 收放);无选中/盒不可解析/未命中返回 null。命中判定以当前可用柄\n * 集合为面——角色过滤与锁定折叠在此一条缝生效。\n */\n resizeHandleAt(sceneX: number, sceneY: number): ResizeHandle | null {\n const selection = this.store.ui.selection\n if (selection === null) return null\n const box = this.boxByPath(selection)\n if (!box) return null\n return resizeHandleHit(\n this.resizeHandlesAt(selection),\n box,\n sceneX,\n sceneY,\n this.store.ui.viewport.zoom,\n )\n }\n\n /**\n * 开始缩放会话:记录目标路径、柄、起点场景坐标与起始解析盒(ui 分支)。\n * 起点记录指针位置而非柄中心——位移以抓取点为基准,移动缘不跳到指针下\n * (与拖动保留抓取偏移同语义,两种主流做法中取不跳变侧)。\n * 已在手势中(拖动/缩放互斥)、路径或盒几何无法解析、路径落在锁定子树、\n * 柄的任一作用轴不在角色可缩放面内(行上拖 e/w、格内容上任意柄)时返回\n * false——gizmo 只渲染可用柄,此守卫兜其余手势来源(键盘/宿主直调)。\n */\n beginResize(path: LayerPath, handle: ResizeHandle, sceneX: number, sceneY: number): boolean {\n if (this.store.ui.drag || this.store.ui.resize || this.store.ui.create) return false\n if (this.isLocked(path)) return false\n if (!RESIZE_HANDLES.includes(handle)) return false\n const doc = this.store.doc\n if (!doc || resolveLayer(doc, path) === null) return false\n const axes = resizableAxesAt(doc, path)\n if (handleResizesWidth(handle) && !axes.width) return false\n if (handleResizesHeight(handle) && !axes.height) return false\n const box = this.boxByPath(path)\n if (!box) return false\n this.store.setResize({ path, handle, startScene: { x: sceneX, y: sceneY }, startBox: box })\n return true\n }\n\n /**\n * 缩放进行中(工单 07):八柄几何求位 + 移动缘吸附 + 文档写回,一个 mergeKey\n * 事务合并成一步历史。\n *\n * 几何:移动缘 = 起始缘 + 场景位移,对缘固定,minSize 钳位不翻转\n * (resizeBox);非本柄轴的位移分量忽略。\n * 吸附:移动缘位点(resizeSnapPoints,角柄双缘、边柄单缘;盒中心不参与缩放\n * 吸附)对轴集合求位——轴集合与拖动同源(snapAxesForGesture),屏幕 6px 阈值\n * 随 zoom 折算;修正施加在指针位移上、再进钳位,吸附不破「对缘固定 + minSize」\n * 语义,吸附结果只改坐标不改尺寸语义(auto 标志采纳是手势起点的轴选择,不随\n * 吸附翻转)。命中的吸附轴回写会话态供吸附线呈现(瞬时回显,endResize 清空)。\n * 写回(工单验收项 2 的语义收口):被拖轴写声明尺寸并清 auto 标志——autoWidth/\n * autoHeight 图层首个位移事务把解析尺寸落地为声明值(「改尺寸标志」语义,\n * 属性面板 auto 前缀输入框的可编辑化同门);未被拖的轴零接触——autoWidth\n * 文本层拖 s 柄后仍随文本自然宽、QR 拖 e 柄高随宽保持正方形(从动轴不采纳)。\n * 写后重断言(updateSpec 同门):autoHeight⟹声明高归零的解码不变量 + 表格\n * 强同步 canonicalize(根表宽联动行宽、格内容宽同步、行高取最高格——行高缩到\n * 最高格以下被不变量顶回属预期)。position 按目标盒反解(锚点偏移随新尺寸\n * 重算,任意锚点/任意父级下落点都精确):同步完成后以草稿内的解析尺寸与\n * 父级盒求锚点偏移,position = 目标盒坐标 − 偏移 − 父级原点(嵌套路径的\n * position 是父级相对量,父级原点经 gestureParentOrigin 在写前状态反解——\n * 根层原点恒 (0,0),行为不变)。\n *\n * 拖不动写不动的同门守卫:无会话、目标层被结构编辑移除(悬空路径)整体\n * 空转(不发事务、不发布命中轴)。\n */\n resizeTo(sceneX: number, sceneY: number): void {\n const gesture = this.store.ui.resize\n if (!gesture) return\n const doc = this.store.doc\n const current = doc ? resolveLayer(doc, gesture.path) : null\n if (!doc || !current) return\n const dx = sceneX - gesture.startScene.x\n const dy = sceneY - gesture.startScene.y\n const widthActive = handleResizesWidth(gesture.handle)\n const heightActive = handleResizesHeight(gesture.handle)\n const resolution = resolveSnapPoints(\n resizeSnapPoints(gesture.startBox, gesture.handle, dx, dy),\n this.snapAxesForGesture(gesture.path),\n snapThresholdScene(this.store.ui.viewport.zoom),\n )\n const target = resizeBox(\n gesture.startBox,\n gesture.handle,\n widthActive ? dx + resolution.dx : dx,\n heightActive ? dy + resolution.dy : dy,\n )\n // 手势空间父级原点(嵌套路径 position 反解的坐标基准),事务前以求得——\n // 须用写前状态(盒/position/解析尺寸同源,见 gestureParentOrigin)\n const origin = this.gestureParentOrigin(gesture.path, current)\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, gesture.path)\n if (!layer) return\n // 被拖轴写声明尺寸并清 auto 标志(采纳);未被拖的轴零接触。\n // draft 语义解除 readonly;字段形态由领域类型与可缩放面把关\n const shape = layer.shape as {\n width: number\n height: number\n autoWidth: boolean\n autoHeight: boolean\n }\n if (widthActive) {\n shape.width = target.width\n shape.autoWidth = false\n }\n if (heightActive) {\n shape.height = target.height\n shape.autoHeight = false\n }\n // 解码不变量重断言 + 表格强同步(updateSpec 同门):先同步再反解\n // position——同步可能改写解析尺寸(格内容宽/行高)与父级盒(行高增长),\n // 反解必须以最终形态为准\n renormalizeAutoHeightInDraft(layer)\n canonicalizeTableSyncInDraft(\n draft,\n gesture.path,\n widthActive ? ['shape', 'width'] : ['shape', 'height'],\n this.textPolicies,\n )\n const resolvedWidth = layerWidth(layer, this.textPolicies)\n const resolvedHeight = layerHeight(layer, this.textPolicies)\n const parent = this.parentDimsInDraft(draft, gesture.path)\n const offset = anchorOffset(\n layer.position.anchor,\n parent.width,\n parent.height,\n resolvedWidth,\n resolvedHeight,\n )\n // draft 语义解除 readonly;position = 目标盒坐标 − 锚点偏移 − 父级原点\n // (任意锚点/任意父级下盒精确落位;父级原点见 gestureParentOrigin)\n const position = layer.position as { x: number; y: number }\n position.x = target.x - offset.x - origin.x\n position.y = target.y - offset.y - origin.y\n }, { mergeKey: RESIZE_MERGE_KEY })\n this.store.setSnapAxes(resolution.axes)\n }\n\n /**\n * 结束缩放:闭合合并事务(下一步历史定格),会话态与命中轴回显清空。\n * 无会话空转(幂等,pointercancel 与 pointerup 双路同达)。\n */\n endResize(): void {\n if (!this.store.ui.resize) return\n this.store.setResize(null)\n this.store.setSnapAxes([])\n this.store.closeMerge(RESIZE_MERGE_KEY)\n }\n\n // ---- 画拉建层(drag-create 工单 01):武装 + 橡皮筋纯视觉 + pointerup 落库 ----\n\n /**\n * 武装建层(面板新增项/层型快捷键的统一入口,CONTEXT「武装」词条):置 ui\n * 分支待命层型,下一次画布按下即开画拉;建层提交或 Esc 即解除——一次性\n * 待命态,无粘性工具(spec 决策 2)。重复武装同型值等短路零通知。\n *\n * preset(text-defaults 工单 01,CONTEXT「文本预设」词条):文本预设 = 写\n * 新层样式槽的快捷方式——命中档位先把档位值并入槽(v1 只 fontSize),再\n * 武装;仅 TextLayer 生效;未知 id = 跳过写槽、武装照常(评审口径统一:\n * 「空转」只指写槽,武装层型本身仍合法)。\n */\n armLayerCreate(type: LayerType, preset?: TextPresetId): void {\n if (preset !== undefined && type === 'TextLayer') {\n const entry = TEXT_PRESETS.find((candidate) => candidate.id === preset)\n if (entry) this.store.setNextTextStyle({ fontSize: entry.fontSize })\n }\n this.store.setArmedCreate(type)\n }\n\n /** 解除建层武装(取消入口):仅清待命态,零副作用。 */\n cancelArmLayerCreate(): void {\n this.store.setArmedCreate(null)\n }\n\n /**\n * 开始画拉会话:须已武装(未武装空转防御——画布手势态机的分派前置,此守卫\n * 兜其余调用来源),与拖动/缩放会话互斥(手势起点一次性定死,beginDrag/\n * beginResize 同门),文档未打开空转。层型在起点一次性捕获进会话(spec\n * 决策 7:拖拽中改主意 = Esc 重武装,v1 不做拖拽中层型切换)。会话只进\n * ui 分支——拖拽期纯视觉、文档零变更(spec 决策 4)。\n */\n beginLayerCreate(sceneX: number, sceneY: number): boolean {\n const armed = this.store.ui.armedCreate\n if (armed === null || this.store.ui.drag || this.store.ui.resize || this.store.ui.create) return false\n if (!this.store.doc) return false\n const point = { x: sceneX, y: sceneY }\n this.store.setCreate({\n type: armed,\n startScene: point,\n currentScene: point,\n rect: { x: sceneX, y: sceneY, width: 0, height: 0 },\n })\n return true\n }\n\n /**\n * 画拉进行中:橡皮筋求位(spatial/create.createRubberBandRect——两点正规化\n * + QR 钳 height := width + 左右/上下缘各两点进 resolveSnapPoints,吸附数学\n * 零新增)写回会话 rect 供 overlay 直读(虚线矩形 + W×H 气泡),命中轴回写\n * ui.snapAxes 走 GuidesOverlay 既有通道回显(dragTo 同门)。吸附排除面为空\n * (snapAxesForGesture(null)——新层尚未入库,全部可见根层供轴,copyMode\n * 首移同门)。零文档事务:修正只进会话几何,落库在 endCreate。\n */\n createTo(sceneX: number, sceneY: number): void {\n const gesture = this.store.ui.create\n const doc = this.store.doc\n if (!gesture || !doc) return\n const resolution = createRubberBandRect(\n gesture.type,\n gesture.startScene,\n { x: sceneX, y: sceneY },\n this.snapAxesForGesture(null),\n snapThresholdScene(this.store.ui.viewport.zoom),\n )\n this.store.setCreate({\n ...gesture,\n currentScene: { x: sceneX, y: sceneY },\n rect: resolution.rect,\n })\n this.store.setSnapAxes(resolution.axes)\n }\n\n /**\n * 结束画拉:按死区分流落库(一次手势 = 一步历史,undo 整体回退落位 + 尺寸\n * + priority)。先清会话再落文档(commitTextEdit 同门——退出路径级联时幂等):\n * - 死区外:单事务写入「createDefaultLayer 缺省形态 + position 左上角对准\n * 橡皮筋矩形(含吸附修正)+ shape.width/height」并 insertRootLayerInDraft\n * 置顶(priority min−1、push 数组尾);\n * - 死区内(点击兜底,位移按 CREATE_DEAD_ZONE_SCREEN_PX 屏幕 px 随 zoom 折算,\n * ALT_DRAG_DEAD_ZONE_SCREEN_PX 同门):缺省尺寸、左上角对准点击点。\n * 落库后自动选中新层并解除武装;返回新层路径(绑定层消费:文本层据此自动\n * 进文本编辑,spec 决策 6)。无会话/无文档空转返回 null。点击兜底不产生\n * 零尺寸层(缺省形态自带可见尺寸)。\n */\n endCreate(): LayerPath | null {\n const gesture = this.store.ui.create\n if (!gesture) return null\n this.store.setCreate(null)\n this.store.setSnapAxes([])\n this.store.setArmedCreate(null)\n if (!this.store.doc) return null\n const dx = gesture.currentScene.x - gesture.startScene.x\n const dy = gesture.currentScene.y - gesture.startScene.y\n const deadZoneScene = CREATE_DEAD_ZONE_SCREEN_PX / this.store.ui.viewport.zoom\n const clicked = Math.hypot(dx, dy) <= deadZoneScene\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n // 新层样式槽覆盖工厂缺省(text-defaults 工单 01):工厂仅 TextLayer 臂消费\n const layer = createDefaultLayer(gesture.type, this.store.ui.nextTextStyle) as Draft<Layer>\n if (clicked) {\n // 点击兜底:缺省尺寸原样,position 左上角对准点击点(拖文件入画布\n // 「左上角对准释放点」同门,spec 决策 5)\n layer.position = { ...layer.position, x: gesture.startScene.x, y: gesture.startScene.y }\n } else {\n layer.position = { ...layer.position, x: gesture.rect.x, y: gesture.rect.y }\n layer.shape.width = gesture.rect.width\n layer.shape.height = gesture.rect.height\n }\n index.v = insertRootLayerInDraft(draft, layer)\n })\n if (index.v < 0) return null\n const path: LayerPath = ['layers', index.v]\n this.store.setSelection(path)\n return path\n }\n\n /**\n * 取消画拉(Esc 语义):清橡皮筋会话、命中轴回显与武装态——零文档写入、\n * 零历史步(拖拽期纯视觉因此取消零回退成本,spec 决策 4)。幂等:无会话时\n * 仅解除武装(待命态 Esc 同走此口)。\n */\n cancelCreate(): void {\n this.store.setCreate(null)\n this.store.setSnapAxes([])\n this.store.setArmedCreate(null)\n }\n\n /**\n * 手势空间父级原点(嵌套路径 position 反解的坐标基准,resizeTo 专用):布局\n * 不变量 box = 父级原点 + 锚点偏移 + position(render.resolveLayerBox 同式)\n * 对被拖层成立,且父级原点在自身缩放中不动——兄弟/祖先几何不被自身尺寸写回\n * 牵动(格宽累加/行高累加只受前序兄弟影响)。故以手势同空间(模板子树经预览\n * 视图解析,与 startBox/startScene 同一坐标系)的当前盒线性反解:原点 = 当前\n * 盒 − 锚点偏移(手势空间父级盒尺寸入参)− 当前 position。根层恒 (0,0)——\n * 既有根层语义(父级 = 画布、原点即画布原点)不变;盒不可解析回落实地 (0,0)\n * (与根层同参,写回仍自洽)。须以写前状态调用:盒/position/解析尺寸三者\n * 同源才能精确反解。\n */\n private gestureParentOrigin(path: LayerPath, layer: Layer): { x: number; y: number } {\n if (isRootLayerPath(path)) return { x: 0, y: 0 }\n const doc = this.store.doc\n if (!doc) return { x: 0, y: 0 }\n const box = this.boxByPath(path)\n if (!box) return { x: 0, y: 0 }\n const parentPath =\n path[path.length - 1] === 'content' ? path.slice(0, -1) : path.slice(0, -2)\n const parentBox = this.boxByPath(parentPath as LayerPath)\n const offset = anchorOffset(\n layer.position.anchor,\n parentBox?.width ?? doc.width,\n parentBox?.height ?? doc.height,\n layerWidth(layer, this.textPolicies),\n layerHeight(layer, this.textPolicies),\n )\n return {\n x: box.x - offset.x - layer.position.x,\n y: box.y - offset.y - layer.position.y,\n }\n }\n\n /**\n * 草稿内求缩放目标的父级盒尺寸(position 反解的锚点偏移入参):根层 = 画布\n * 尺寸(resolveLayerBox 根层语义同参);行/格取容器路径(rows/cells 段成对、\n * content 单段),经 layerBoxByPath 在草稿内解析——同步完成后的父级盒(行高\n * 增长等同步效应已含)。悬空回落画布尺寸(与根层同参,position 仍自洽)。\n */\n private parentDimsInDraft(draft: Draft<Canvas>, path: LayerPath): { width: number; height: number } {\n if (isRootLayerPath(path)) return { width: draft.width, height: draft.height }\n const parentPath =\n path[path.length - 1] === 'content' ? path.slice(0, -1) : path.slice(0, -2)\n const box = layerBoxByPath(draft, parentPath as LayerPath, this.textPolicies)\n return box ?? { width: draft.width, height: draft.height }\n }\n\n /**\n * 方向键微调(kbd-nav 工单 02):选中层 position 按场景增量位移(注册表 8\n * 条目方向 × 步长归并到此,executeShortcut 传 ±1/±10)。作用面与拖动同门——\n * 选中什么微调什么,位置写法与 dragTo 同一语义(position 原位 += 增量);\n * 拖不动的空转(无选择/无文档/路径不可解析)。**不吸附**:吸附是拖动过程的\n * 连续行为(CONTEXT「微调」词条边界),微调是可预测的定量位移,不走\n * resolveSnap、不回显命中轴。\n * 连续微调合并一条历史:mergeKey 含选中层路径、方向无关——同层连按(跨方向)\n * 并成一步,undo 一次回连按前;换层即换步(mergeKey 随路径变);undo/redo\n * 走 store 既有断开规则(closeMerge)。断开是键控而非选择事件驱动:选择往返\n * (A→B→A 中途无文档变更)会并回 A 的原串,机制既有规则同款。拖动/缩放会话\n * 进行中空转(防插入事务拆分开放中的合并步,工单 07 缩放同门)。\n * 守卫(canvas-web-layer-lock 工单 01 挂账兑现):锁定子树空转(isLocked\n * 谓词同门——微调是变更类动作)。空转返回 false(executeShortcut 可用态口径)。\n */\n nudge(deltaX: number, deltaY: number): boolean {\n const path = this.store.ui.selection\n if (path === null || this.store.ui.drag !== null || this.store.ui.resize !== null) return false\n if (this.isLocked(path)) return false\n const doc = this.store.doc\n if (!doc || resolveLayer(doc, path) === null) return false\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path)\n if (!layer) return\n // immer draft 原位写 position;类型层的 readonly 由 draft 语义解除\n const position = layer.position as { x: number; y: number }\n position.x += deltaX\n position.y += deltaY\n }, { mergeKey: nudgeMergeKey(path) })\n return true\n }\n\n // ---- 参考线与标尺(ruler-guides-snap 工单 01):会话态住 ui 分支,零契约面 ----\n\n /**\n * 新增参考线(标尺拖出落线的写入口,工单 03 消费):水平/垂直轴、场景坐标。\n * 住 ui 分支——不进历史、不写 graph(ADR 0012);id 会话内自增供删除寻址。\n * 非有限坐标空转返回 null(绑定层异常输入防御,不产生通知)。\n */\n addGuide(guide: { orientation: GuideOrientation; position: number }): Guide | null {\n if (!Number.isFinite(guide.position)) return null\n const stored: Guide = { id: ++this.guideIdSeq, orientation: guide.orientation, position: guide.position }\n this.store.setGuides([...this.store.ui.guides, stored])\n return stored\n }\n\n /** 删除参考线(拖回标尺语义):按 id 移除;未知 id 空转返回 false */\n removeGuide(id: number): boolean {\n const guides = this.store.ui.guides\n const next = guides.filter((guide) => guide.id !== id)\n if (next.length === guides.length) return false\n this.store.setGuides(next)\n return true\n }\n\n /**\n * 移动参考线(抓取拖动再定位的写入口):按 id 原地改位(取向不变),\n * ui 分支同门——不进历史、不写 graph(ADR 0012),广播 guides 分支通知\n * (覆盖层失效重绘)。非有限坐标/未知 id 空转返回 false(不产生通知);\n * 同位空转返回 true(无变化不惊动订阅方——抓取未动的松手路径)。\n */\n updateGuide(id: number, position: number): boolean {\n if (!Number.isFinite(position)) return false\n const guides = this.store.ui.guides\n const target = guides.find((guide) => guide.id === id)\n if (target === undefined) return false\n if (target.position === position) return true\n this.store.setGuides(guides.map((guide) => (guide.id === id ? { ...guide, position } : guide)))\n return true\n }\n\n /**\n * 清空全部参考线(工具栏「清空参考线」写入口,工单 03 消费):ui 分支整体置空,\n * 与 removeGuide 同型广播 guides 分支通知(覆盖层失效重绘)。会话级语义同门——\n * 不进历史、不写 graph(ADR 0012),不可撤销;无文档或已空时空转(不产生通知)。\n */\n clearGuides(): void {\n if (!this.store.doc) return\n if (this.store.ui.guides.length === 0) return\n this.store.setGuides([])\n }\n\n /** 参考线列表(只读查询,呈现与吸附供轴同源) */\n listGuides(): readonly Guide[] {\n return this.store.ui.guides\n }\n\n /** 标尺显隐开关(⇧R 分派口):翻转 ui 偏好,不进历史 */\n toggleRulers(): void {\n this.store.setRulersVisible(!this.store.ui.rulersVisible)\n }\n\n // ---- 编辑器状态导出与恢复(project-data 工单 01,spec §5):与 addGuide/toggleLayerLock 同门——ui 分支写、不进历史、不写 graph ----\n\n /**\n * 导出当前打开画布的编辑器状态切片:guides 原样带出(id 是会话内自增值,\n * 宿主不持久化其语义——restore 端重排重取号)、lockedPaths 原样。无文档也可\n * 导出(ui 分支自成事实,addGuide 无 doc 守卫同门)。内核帧盲:宿主把各帧\n * 切片按帧名组装成 frames 容器,加上 schemaVersion(EDITOR_STATE_SCHEMA_VERSION)\n * 与 getEditorPrefs() 才是完整工程数据快照,经宿主层存储——容器不进内核。\n */\n exportEditorState(): CanvasEditorState {\n return { guides: this.store.ui.guides, lockedPaths: this.store.ui.lockedPaths }\n }\n\n /**\n * 恢复当前画布的状态切片(prefs 不在此——内核跨 openDocument 自持):轻结构\n * 校验(形状不合法整体拒,返 false 宿主记因)→ 整体替换 lockedPaths →\n * pruneDanglingPaths(悬空锁路径剔除——合法但内容悬空不算拒,prune 兜底)→\n * guide id 重排(清空后逐条 addGuide 重取号:快照 id 无持久语义,原样入栈必\n * 与新会话 guideIdSeq 撞号致 removeGuide 误删)。不进历史、不写 graph(红线\n * 同门)。\n */\n restoreEditorState(state: CanvasEditorState): boolean {\n const slice = parseCanvasEditorState(state)\n if (slice === null) return false\n this.store.setLockedPaths(slice.lockedPaths)\n this.pruneDanglingPaths()\n if (this.store.ui.guides.length > 0) this.store.setGuides([])\n for (const guide of slice.guides) this.addGuide(guide)\n return true\n }\n\n /**\n * 只读偏好(保存 prefs 的读取口):与 store.ui 同源,给宿主一个不用下钻\n * store 的口。读 = getEditorPrefs,写 = applyEditorPrefs,对称成对;帧切片\n * 走 restoreEditorState,两不相混。\n */\n getEditorPrefs(): EditorPrefsState {\n const ui = this.store.ui\n return {\n rulersVisible: ui.rulersVisible,\n anchorExpanded: ui.anchorExpanded,\n nextTextStyle: ui.nextTextStyle,\n }\n }\n\n /**\n * 应用偏好(会话建立时一次性,跨会话恢复 prefs 的写入口):整体替换\n * rulersVisible/anchorExpanded,不进历史;值等写入经 store 短路零通知。\n * 新层样式(text-defaults 工单 01)走宽容读逐字段合并(pickNextTextStylePatch\n * 共享谓词——与捕获同门,缺字/类型不符保留现值)。\n */\n applyEditorPrefs(prefs: EditorPrefsState): void {\n this.store.setRulersVisible(prefs.rulersVisible)\n this.store.setAnchorExpanded(prefs.anchorExpanded)\n this.store.setNextTextStyle(pickNextTextStylePatch(prefs.nextTextStyle))\n }\n\n // ---- 对齐画布(layer-align-snap 工单 01):图层盒整体对齐画布几何 ----\n\n /**\n * 对齐画布 action 族(对齐画布/贴边/贴角,ADR 0011):把 path 处图层盒整体\n * 对齐到画布几何——mode 十种:贴边 left/right/top/bottom(零边距)、居中\n * h-center/v-center、贴角 corner-tl/tr/bl/br(opts.margin 统一边距,缺省\n * ALIGN_CORNER_MARGIN_PX)。与属性面板「盒内对齐」(写 align 字段移动盒内\n * 内容)是两类动作,并存不回退。\n * 几何以 layerBoxAt(path) 解析的图层盒为唯一来源、画布尺寸取 doc.canvas;\n * 盒坐标对 position 线性(box = origin + anchorOffset + position),「目标\n * 盒坐标 − 当前盒坐标」的位移原样写 position 偏移、anchor 不动;盒大于画布\n * 不钳位(偏移可为负的领域语义,溢出侧按公式确定)。一次调用 = 一步历史\n * (无 mergeKey,连续点击不合并);已对齐(零位移)经 store 空 patch 短路\n * 不进历史;文档未打开/路径不可解析/未知 mode 空转(无历史步)。\n */\n alignToCanvas(path: LayerPath, mode: AlignToCanvasMode, options: AlignToCanvasOptions = {}): void {\n const doc = this.store.doc\n if (!doc) return\n const box = this.boxByPath(path)\n if (!box) return\n const target = alignToCanvasTarget(\n mode,\n box,\n doc.width,\n doc.height,\n options.margin ?? ALIGN_CORNER_MARGIN_PX,\n )\n if (!target) return\n const dx = target.x - box.x\n const dy = target.y - box.y\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path)\n if (!layer) return\n // draft 语义解除 readonly;盒位移与 position 位移一一对应(斜率 1)\n const position = layer.position as { x: number; y: number }\n position.x += dx\n position.y += dy\n })\n }\n\n // ---- 文本编辑会话(工单 11):会话住 ui 分支,提交经漏斗一次性落文档 ----\n\n /**\n * 进入文本编辑:路径必须解析到 TextLayer。会话只进 ui 分支(不进历史),\n * live 文本住在绑定层的 textarea,内容层随即跳绘该层文本(防重影)。\n *\n * 内容类型会话标志(canvas-web-expression-editing 工单 01,spec 决策 2)按\n * 进入瞬间 `layer.expression !== null` 锚定进会话快照——「进入时计、编辑中\n * 稳定」(verticalAnchorY 同门):编辑中面板改标记不回写标志,提交去向由\n * 快照裁决(打标是显式动作,内容判定制已否决)。同路径重复进入短路(快照\n * 不重锚);换层进入按新层现状重锚。\n */\n beginTextEdit(path: LayerPath): boolean {\n const layer = this.textLayerAt(path)\n if (layer === null) return false\n const current = this.store.ui.editing\n if (current && pathsEqual(current.path, path)) return true\n this.store.setEditing({ path, expression: layer.expression !== null })\n return true\n }\n\n /**\n * 提交漏斗的统一出口:Esc / Ctrl+Enter / blur / 点画布他处四条退出路径都收拢\n * 到这里。先清会话再落文档——退出路径可能级联触发(画布点按的 pointerdown\n * 提交与随后 blur 竞态),会话清空后后续调用幂等,保证一次退出恰一步历史。\n *\n * 提交矩阵(canvas-web-expression-editing 工单 01,spec 决策 3,空串判定先于\n * 片段判定):\n * - 串与文档一致:不进历史(进出编辑零噪声),返回 false——对比对象随会话\n * 模式取:表达式会话对比 `layer.expression`、字面会话对比 `layer.text`(现行\n * 语义);\n * - 空串:删除该图层(复用 deleteLayer 的 splice/重映射语义),任何模式同语义;\n * - 表达式会话且串含 ≥1 语法有效闭合片段(hasValidExpressionFragment):\n * updateDataExpression 标记写(保持标记、text 自动重镜像);\n * - 其余(字面会话恒走此支;表达式会话无合法片段回落):updateData 字面写、\n * 字面接管解标——含 `{{...}}` 按字面保留,杜绝无片段标记层。\n */\n commitTextEdit(text: string): boolean {\n const editing = this.store.ui.editing\n if (!editing) return false\n this.store.setEditing(null)\n\n const layer = this.textLayerAt(editing.path)\n if (!layer) return false\n if (editing.expression ? text === layer.expression : text === layer.text) return false\n if (text === '') {\n this.deleteLayer(editing.path)\n return true\n }\n if (editing.expression && hasValidExpressionFragment(text)) {\n this.updateDataExpression(editing.path, text)\n return true\n }\n this.updateData(editing.path, text)\n return true\n }\n\n /**\n * 编辑 overlay 的布局描述:与绘制/命中同一套布局策略求出的盒几何与排版字段\n * (textarea 以盒左上对位、盒尺寸占位,字号/行高/内边距按场景像素交给 CSS\n * transform 缩放)。verticalAnchorY 按进入编辑时的文档文本计算,编辑中保持\n * 稳定不随键入跳动。非文本层/路径失效返回 null。\n */\n textEditLayout(path: LayerPath): TextEditLayout | null {\n const layer = this.textLayerAt(path)\n const doc = this.store.doc\n if (!layer || !doc) return null\n const box = this.boxByPath(path)\n if (!box) return null\n const editing = this.store.ui.editing\n return {\n box,\n text: layer.text,\n font: layer.font,\n fontColor: layer.fontColor,\n fontSize: layer.fontSize,\n lineHeightPx: lineHeightPx(layer),\n padding: layer.shape.padding,\n horizontalAlign: layer.align.horizontal,\n verticalAlign: layer.align.vertical,\n verticalAnchorY: textOrigin(layer, this.textPolicies).y,\n autowrap: layer.autowrap,\n expressionMode:\n editing !== null && pathsEqual(editing.path, path)\n ? editing.expression\n : layer.expression !== null,\n }\n }\n\n /** 编辑会话的目标图层查询:路径须解析到 TextLayer,其余(含无文档)返回 null */\n private textLayerAt(path: LayerPath): TextLayer | null {\n const doc = this.store.doc\n const layer = doc ? resolveLayer(doc, path) : null\n return layer && layer.type === 'TextLayer' ? layer : null\n }\n\n // ---- 查找替换(canvas-web-find-replace 工单 01):会话态住 ui 分支,扫描/替换纯函数在 editing/findReplace ----\n\n /**\n * 打开查找会话(⌘F 分派口):无文档空转返回 false;已开时值等短路零通知\n * (重复 ⌘F 的重新聚焦归绑定层)。查询词/替换词/游标跨关开保留(closeFind\n * →beginFind 同文档连续改字),openDocument 才整体重置。\n */\n beginFind(): boolean {\n if (!this.store.doc) return false\n this.store.setFind({ ...this.store.ui.find, open: true })\n return true\n }\n\n /** 关闭查找会话(Esc 归面板组件调用):仅翻开合面,查询词与游标保留 */\n closeFind(): void {\n this.store.setFind({ ...this.store.ui.find, open: false })\n }\n\n /** 查找词写入(查找条输入即扫的源头):查询词变更即重置游标(旧下标对新命中集无意义) */\n setFindQuery(query: string): void {\n const find = this.store.ui.find\n this.store.setFind({ ...find, query, cursor: find.query === query ? find.cursor : 0 })\n }\n\n /** 替换词写入 */\n setFindReplacement(replacement: string): void {\n this.store.setFind({ ...this.store.ui.find, replacement })\n }\n\n /** 游标写入(上一个/下一个导航口):存原值,越界在读侧钳位 */\n setFindCursor(cursor: number): void {\n this.store.setFind({ ...this.store.ui.find, cursor })\n }\n\n /**\n * 派生命中列表(overlay 高亮与查找条计数同源消费):每次从 doc + query 现算、\n * 不驻留(树小无压力,免 pruneDanglingPaths 式失配维护);视觉序 = 数组尾→头\n * (hitTest 同门),命中 = path + 偏移对。\n */\n listFindMatches(): readonly FindTextHit[] {\n const doc = this.store.doc\n if (!doc) return []\n return scanTextMatches(doc, this.store.ui.find.query)\n }\n\n /** 当前游标(读侧钳位到派生命中列表:越界落末处/首处,空命中为 0) */\n get findCursor(): number {\n return clampFindCursor(this.store.ui.find.cursor, this.listFindMatches().length)\n }\n\n /**\n * 替换当前命中(查找条「替换」按钮):一次无 mergeKey 事务 = 一步历史;游标\n * 原地不动——事务后派生列表在被替换处缩一位,原下标即「下一处」(「替换后\n * 自动跳下一处」靠派生天然成立,末处替换则钳位停在尾)。写值经 replaceHitsInDraft\n * 与文本编辑提交同门(text 直写 + expression 保持 null)。无文档/空 query/\n * 无命中空转返回 false。\n */\n replaceOne(): boolean {\n const doc = this.store.doc\n const query = this.store.ui.find.query\n if (!doc || query === '') return false\n const matches = scanTextMatches(doc, query)\n const hit = matches[clampFindCursor(this.store.ui.find.cursor, matches.length)]\n if (!hit) return false\n this.store.transact((draft) => {\n replaceHitsInDraft(draft, [hit], this.store.ui.find.replacement)\n })\n return true\n }\n\n /**\n * 全部替换(查找条「全部替换」按钮):命中(path + 偏移对)在事务前按原文\n * 快照一次算定,单事务内按同字段偏移降序应用、替换产物不重扫(replacement\n * 含查询串不死循环)——一次调用 = 一步历史,undo 一次全回。返回应用处数\n * (计数反馈「已替换 N 处」);无文档/空 query 空转返回 0。\n */\n replaceAll(): number {\n const doc = this.store.doc\n const query = this.store.ui.find.query\n if (!doc || query === '') return 0\n const hits = scanTextMatches(doc, query) // 原文快照:命中事务前一次算定\n if (hits.length === 0) return 0\n let applied = 0\n this.store.transact((draft) => {\n applied = replaceHitsInDraft(draft, hits, this.store.ui.find.replacement)\n })\n return applied\n }\n\n // ---- 属性写入(工单 09):面板零直改,一切文档字段编辑经这三个 action ----\n\n /**\n * 更新图层 spec 子树字段(属性面板的权威写入口):key 为图层内字段路径\n * (领域形态,如 ['shape','backgroundColor']、['position','x']、单段 ['fontSize']),\n * 拼上图层路径即 patch path。数值收整/钳位等取值策略由注册表层负责,内核透传。\n * 路径无法解析或字段悬空时静默空转;连续输入经 mergeKey 合并为一步历史。\n *\n * 表格子树的权威收口(工单 12):格 autoHeight 是带重算的切换(采纳内容动态高\n * 并固化,非裸标志写);其余写入后按解码强同步的不变量重断言(行宽=表宽、\n * 内容宽/高重同步、行高取最高格)——往返恒等在任何字段编辑后不破。\n */\n updateSpec(path: LayerPath, key: readonly string[], value: unknown, options: TransactOptions = {}): void {\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path)\n if (!layer) return\n // 模板格跳过采纳语义(决策 2026-09):模板态高度耦合全豁免(ADR 0006,\n // 解码对声明高/autoHeight 原样保留、仅 auto 标志清零声明高),下方裸写 +\n // 归一即解码同门;非模板格的带重算切换不适用\n if (\n layer.type === 'TableCellLayer'\n && !isTemplateSubtreePath(path)\n && key.length === 2\n && key[0] === 'shape'\n && key[1] === 'autoHeight'\n ) {\n setCellAutoHeightInDraft(draft, path, Boolean(value), this.textPolicies)\n return\n }\n // draft 语义解除 readonly;字段形态由注册表与领域类型把关,内核透传\n writeSpecField(layer, key, value)\n // 解码不变量:autoHeight 置位即声明高归零(renormalizeAutoHeightInDraft\n // 收口),否则保存再打开声明高被清,往返恒等破。\n renormalizeAutoHeightInDraft(layer)\n canonicalizeTableSyncInDraft(draft, path, key, this.textPolicies)\n }, options)\n this.captureNextTextStyle(path, key, value)\n }\n\n /**\n * 新层样式捕获(text-defaults 工单 01,CONTEXT「新层样式」词条):属性面板\n * 对 TextLayer 文本族五字段的权威写顺带更新偏好槽——「用户改过一次,下一个\n * 新层沿用」。捕获口唯一(updateSpec 漏斗):内容写(updateData)、样式粘贴\n * (搬运不是偏好)、撤销重做都不经过此口。字段合法性走 editing 域共享谓词\n * pickNextTextStylePatch(与 prefs 宽容读同门,评审合流不再双写):目标层非\n * TextLayer、键不命中或值类型不符即空转,符合字段经值等短路入槽。\n */\n private captureNextTextStyle(path: LayerPath, key: readonly string[], value: unknown): void {\n const layer = this.store.doc ? resolveLayer(this.store.doc, path) : null\n if (!layer || layer.type !== 'TextLayer') return\n const field = captureFieldOfKey(key)\n if (field === null) return\n const patch = pickNextTextStylePatch({ [field]: value })\n if (patch[field] !== undefined) this.store.setNextTextStyle(patch)\n }\n\n /**\n * 更新图层数据字段(wire data.value 的领域展开,按 type 分派):\n * TextLayer → text、ImageLayer → src、QrCodeLayer → value;\n * 空串/null 归空语义与解码逐条对齐(Image null、Text/Qr 空串)。\n * 字面写解除标记(工票 02,镜像 PHP 三内容层 setter):写值即置 expression =\n * null,值与标记的镜像关系随之解除——标记态文档经字面编辑退化为字面态,\n * 不可自动恢复属预期(spec §3.7 兼容面)。\n * 表/行/格无数据字段,空转。\n */\n updateData(path: LayerPath, value: string | null, options: TransactOptions = {}): void {\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path) as Draft<Layer> | null\n if (!layer) return\n switch (layer.type) {\n case 'TextLayer':\n layer.text = value == null ? '' : String(value)\n layer.expression = null\n break\n case 'ImageLayer':\n layer.src = value == null || value === '' ? null : String(value)\n layer.expression = null\n break\n case 'QrCodeLayer':\n layer.value = value == null ? '' : String(value)\n layer.expression = null\n break\n default:\n break\n }\n }, options)\n }\n\n /**\n * 数据字段表达式标记写入口(打标/解标,spec:canvas-web-expression-marking §1;\n * 镜像 PHP 三内容层 setExpression 与 decode 标记/未标记分支):\n * - expression 非 null = 打标:expression 原文 + 值字段恒镜像原文(Text.text /\n * Image.src / QrCode.value);标记态不做空串 →null 归一(decodeImageLayer\n * 标记分支同门)。\n * - expression = null = 解标:值字段保持现值退字面,`expression = null`;\n * 字面态空值归一交给 updateData/decode 分支(这里不清值,字面接管语义)。\n * - 仅三内容层生效,表/行/格/行模板空转(与 updateData 同门)。\n * - updateData 语义不变:字面写解除标记仍成立(spec §1 明示两者并排)。\n */\n updateDataExpression(path: LayerPath, expression: string | null, options: TransactOptions = {}): void {\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path) as Draft<Layer> | null\n if (!layer) return\n switch (layer.type) {\n case 'TextLayer':\n layer.expression = expression == null ? null : String(expression)\n if (layer.expression !== null) layer.text = layer.expression\n break\n case 'ImageLayer':\n layer.expression = expression == null ? null : String(expression)\n if (layer.expression !== null) layer.src = layer.expression\n break\n case 'QrCodeLayer':\n layer.expression = expression == null ? null : String(expression)\n if (layer.expression !== null) layer.value = layer.expression\n break\n default:\n break\n }\n }, options)\n }\n\n /** 更新画布级字段(未选中图层时面板的宽/高写入口);键由类型收窄为 width/height */\n updateCanvasProp(key: 'width' | 'height', value: number, options: TransactOptions = {}): void {\n this.store.transact((draft) => {\n draft[key] = value\n }, options)\n }\n\n // ---- 数据源 schema 声明(content-completion 工单 03/08):D2 宿主随会话注入 ----\n\n /**\n * 注入数据源 schema 声明(载荷形态,D1 钉定:声明即 compile(canvas, dataset)\n * 收到的 data 载荷形状,根上下文候选 = 载荷顶层键)。声明经\n * normalizeExpressionSchemaSource 一次性编译收口——根级结构性问题(保留键\n * row/$ 前缀,前移填充期 reserved_root_key;根非对象/缺 properties 层级)降级\n * 为无候选 + console 警告,局部故障($ref 断链/外部指针/环引用/目标形态不符)\n * 不拒绝:该节点降叶子 + 注入期汇总告警一次,树其余部分照常服务,不抛错不\n * 弹错(辅助声明不作权威);null/undefined = 清除声明。声明只住 store ui 分支:\n * 不进 graph、不落 localStorage、不进 wire;openDocument 换文档不重置,\n * 重注入/清除走同一入口。签名与「注入即整体替换」语义不变(D6)。\n */\n setDataSourceSchema(raw: unknown): void {\n this.store.setDataSourceSchema(\n raw === null || raw === undefined ? null : normalizeExpressionSchemaSource(raw),\n )\n }\n\n // ---- 上传(工单 13):本机资源 → 可物化引用,实现经 uploadHandler 注入 ----\n\n /** 上传可用性:未注入 uploadHandler 时为 false(宿主据此隐藏/禁用上传入口) */\n get canUpload(): boolean {\n return this.uploadHandler !== null\n }\n\n /** 字体清单(内置可配置 + 自定义上传追加;清单 ≠ 物化,加载走渲染端物化管线) */\n get fontCatalog(): FontCatalog {\n return this.fontCatalogValue\n }\n\n /**\n * 本机图片 → 新增图片图层并写入可物化引用(全流程 = 一步历史):上传经注入\n * 实现,引用与 addRootLayerInDraft 落同一事务(撤销一次即回退整个上传建层),\n * 完成后自动选中新层。文档未打开直接返回 null(不产生上传副作用——字节不离开\n * 本机);上传失败异常上抛、文档不动。placement(kbd-nav 工单 05)缺省 =\n * 编辑器缺省盒;`at` 图层盒左上角对准场景点、`size` 1:1 落盒不缩放(拖文件\n * 入画布语义,自然尺寸由绑定层解码传入——内核无 DOM)。\n */\n async uploadImageAsLayer(file: UploadFile, placement: UploadImagePlacement = {}): Promise<LayerPath | null> {\n if (!this.store.doc) return null\n const ref = await this.requireUpload(file)\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n index.v = addRootLayerInDraft(draft, 'ImageLayer')\n if (index.v < 0) return\n const layer = draft.layers[index.v] as Draft<ImageLayer>\n layer.src = ref\n if (placement.at) layer.position = { ...layer.position, x: placement.at.x, y: placement.at.y }\n if (placement.size) {\n layer.shape.width = placement.size.width\n layer.shape.height = placement.size.height\n }\n })\n if (index.v < 0) return null\n const path: LayerPath = ['layers', index.v]\n this.store.setSelection(path)\n return path\n }\n\n /**\n * 本机图片 → 可物化引用(裸上传,不建层不写文档):引用落到哪个图片层由\n * 消费方决定(属性面板图片控件),与 uploadFont 同门的 bare 形态。文档未打开\n * 直接返回 null(不产生上传副作用——字节不离开本机);未注入 uploadHandler\n * 抛 UploadHandlerMissingError;handler 失败异常上抛。\n */\n async uploadImage(file: UploadFile): Promise<string | null> {\n if (!this.store.doc) return null\n return this.requireUpload(file)\n }\n\n /**\n * 本机字体 → 可物化引用并加入字体清单(自定义条目,上传后即可选可用)。\n * 不写文档:引用落到哪个文本层由宿主/绑定层决定(属性面板字体控件)。\n */\n async uploadFont(file: UploadFile): Promise<string> {\n const ref = await this.requireUpload(file)\n this.fontCatalogValue.addCustom({ label: uploadDisplayName(file), ref })\n return ref\n }\n\n private requireUpload(file: UploadFile): Promise<string> {\n if (this.uploadHandler === null) return Promise.reject(new UploadHandlerMissingError())\n return this.uploadHandler(file)\n }\n\n // ---- 图层面板结构编辑(工单 10):重排/增删走单一 action,重排两套语义分立 ----\n\n /**\n * 根层重排(图层面板拖动):面板坐标(0 = 视觉最上层),to 为 insert-before\n * 落点。数组序 splice + 移动层 priority 中点插值(保持「数组按 priority 降序」\n * 的契约语义,保存再打开顺序不变),一次调用 = 一步历史。语义细节见\n * layerPanel.moveRootLayerInDraft。\n */\n moveRootLayer(fromPanel: number, toPanel: number): void {\n if (!this.store.doc) return\n const moved: TxOut<{ from: number; to: number } | null> = { v: null }\n this.store.transact((draft) => {\n moved.v = moveRootLayerInDraft(draft, fromPanel, toPanel)\n })\n if (moved.v === null) return\n this.remapPathSlices([], 'layers', moved.v.from, moved.v.to)\n }\n\n /**\n * 表格行重排(图层面板行拖动):直接改 rows 数组序(容器内嵌套数组序语义,\n * priority 不参与,与根层语义分立),一次调用 = 一步历史。行内后代的选择/\n * 悬停路径随行重映射。\n */\n moveTableRow(tablePath: LayerPath, fromRow: number, toRow: number): void {\n if (!this.store.doc) return\n const moved: TxOut<{ from: number; to: number } | null> = { v: null }\n this.store.transact((draft) => {\n moved.v = moveTableRowInDraft(draft, tablePath, fromRow, toRow)\n })\n if (moved.v === null) return\n this.remapPathSlices(tablePath, 'rows', moved.v.from, moved.v.to)\n }\n\n /**\n * 新增根层(图层面板「新增」入口):工厂缺省形态 + 置顶 priority = min − 1\n * (空画布 0),push 到数组尾(视觉最上层)并自动选中新层;一次调用 = 一步历史。\n */\n addRootLayer(type: LayerType): void {\n if (!this.store.doc) return\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n // 新层样式槽覆盖工厂缺省(text-defaults 工单 01):工厂仅 TextLayer 臂消费\n index.v = addRootLayerInDraft(draft, type, this.store.ui.nextTextStyle)\n })\n if (index.v >= 0) this.store.setSelection(['layers', index.v])\n }\n\n /**\n * 删除图层(含子树):根层整删、行/格 splice、格内容置 null(语义见\n * layerPanel.deleteLayerInDraft);选中/悬停落在被删子树内即清空,其余路径\n * 平移重映射。一次调用 = 一步历史。\n * 模板子树删除面(spec §3.2):cells/content 两级放行(内核两型 union),\n * 仅行模板替身(template 收尾路径)拦截——删行模板 = 表退出模板态,归转换\n * 入口(convertTableToRows),不混入删除。\n * 锁定 guard(canvas-web-layer-lock 工单 01):路径落在锁定子树(锁定的根层\n * 及其行/格/内容)即静默空转——Delete/⌫ 与面板删除按钮的权威挡点,无历史步。\n */\n deleteLayer(path: LayerPath): void {\n if (!this.store.doc || path[path.length - 1] === 'template') return\n if (this.isLocked(path)) return\n const removed: TxOut<DeletedLayerRef | null> = { v: null }\n this.store.transact((draft) => {\n removed.v = deleteLayerInDraft(draft, path)\n })\n if (removed.v === null) return\n if (removed.v.kind === 'list') {\n this.remapPathSlices(removed.v.containerPath, removed.v.key, removed.v.index, removed.v.index)\n return\n }\n // 格内容删除:无下标平移,命中该路径的选择/悬停清空\n if (pathsEqual(this.store.ui.selection, removed.v.path)) this.store.setSelection(null)\n if (pathsEqual(this.store.ui.hovered, removed.v.path)) this.store.setHovered(null)\n }\n\n /**\n * 重命名根图层(layer-panel-ux 工单 09):一次调用 = 一步历史(可撤销)。\n * 仅根图层——行/格是容器内结构(显示层「行 N / 格 N」派生标签),路径非根\n * 或不可解析一律空转(无历史步);空名 = 回退缺省(文档 name 归空串,\n * wire 键随之省略,显示层回落派生标签)。不改结构与 priority,选择/悬停\n * 路径不受影响。\n */\n renameLayer(path: LayerPath, name: string): void {\n if (!this.store.doc || !isRootLayerPath(path)) return\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path) as Draft<Layer> | null\n if (!layer) return\n layer.name = name\n })\n }\n\n /**\n * 开始重命名会话(图层面板双击/铅笔/F2 的统一入口):路径必须解析到根图层。\n * 会话只进 ui 分支(不进历史),输入框的草稿文本住绑定层,提交经\n * commitRename 漏斗一次性落文档。\n */\n beginRename(path: LayerPath | null): boolean {\n if (path === null || !this.store.doc || !isRootLayerPath(path)) return false\n if (resolveLayer(this.store.doc, path) === null) return false\n this.store.setRenaming(path)\n return true\n }\n\n /**\n * 重命名提交漏斗的统一出口:Enter / blur 两条退出路径都收拢到这里。先清\n * 会话再落文档——退出路径可能级联触发(Enter 提交后 input 卸载的 blur),\n * 会话清空后后续调用幂等,保证一次退出恰一步历史。空名直传 renameLayer\n * (回退缺省语义在内核);与现名相同不产生历史步(进出编辑零噪声)。\n */\n commitRename(name: string): boolean {\n const renaming = this.store.ui.renaming\n if (renaming === null) return false\n this.store.setRenaming(null)\n const layer = this.store.doc ? resolveLayer(this.store.doc, renaming) : null\n if (!layer || layer.name === name) return false\n this.renameLayer(renaming, name)\n return true\n }\n\n /** 取消重命名会话:清 ui 态不落文档(Esc 语义) */\n cancelRename(): void {\n this.store.setRenaming(null)\n }\n\n /**\n * 切换根图层显示/隐藏(layer-panel-ux 工单 10):一次调用 = 一步历史(可撤销)。\n * 仅根图层——visible 在 LayerBase 面(渲染端整层跳过,行/格是容器内结构无独立\n * 可见性),路径非根或不可解析一律空转(无历史步),与 renameLayer 同门。\n * 隐藏的下游语义:渲染跳过(工单 01 契约)+ 命中/gizmo 过滤(spatial/hitTest、\n * 绑定层 gizmo),编辑器 UI 只消费这里的写入口。\n */\n toggleLayerVisibility(path: LayerPath): void {\n if (!this.store.doc || !isRootLayerPath(path)) return\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path) as Draft<Layer> | null\n if (!layer) return\n layer.visible = !layer.visible\n })\n }\n\n /**\n * 切换根图层锁定(canvas-web-layer-lock 工单 01):会话级锁定态,ui 分支\n * 变更——不进历史、不写 graph、wire 零键(红线 3 同门),openDocument 重置。\n * 仅根图层——行/格是容器内结构(锁定语义只在 LayerBase 面),路径非根或\n * 不可解析一律空转(renameLayer/toggleLayerVisibility 同门)。\n * 下游语义:锁定层整子树退出画布命中面(hitTest 过滤)、拖动起点/删除在\n * 内核空转(方向键微调已随 kbd-nav 工单 02 接入同一谓词);属性编辑/duplicate/\n * 显隐/改名/z 序等刻意通道不受限;⌘D 副本新路径天然无锁。\n */\n toggleLayerLock(path: LayerPath): void {\n if (!this.store.doc || !isRootLayerPath(path)) return\n if (resolveLayer(this.store.doc, path) === null) return\n const current = this.store.ui.lockedPaths\n const next = current.some((locked) => pathsEqual(locked, path))\n ? current.filter((locked) => !pathsEqual(locked, path))\n : [...current, path]\n this.store.setLockedPaths(next)\n }\n\n /**\n * 锁定谓词(canvas-web-layer-lock 工单 01,ui 分支读取口):路径落在锁定\n * 子树即 true,判定收口 isLockedPath——hitTest 过滤、动作 guard、gizmo、\n * 面板投影共用,绑定层不得自行手写锁定路径比对。\n */\n isLocked(path: LayerPath | null): boolean {\n return path !== null && isLockedPath(path, this.store.ui.lockedPaths)\n }\n\n // ---- 剪贴板与置顶/置底(工单 14):子树深拷贝语义见 clipboard.ts ----\n\n /**\n * 复制当前选中层:子树深拷贝快照进会话剪贴板,并捕获复制时点的绝对盒(粘贴\n * 落位基准)与源路径(sourcePath——粘贴时紧邻插入的解析凭据,alt-drag-paste\n * 工单 02)。源须为可落根层的类型(行/格是容器内结构,v1 不可复制);无选择/\n * 不可复制为 false。\n */\n copySelection(): boolean {\n if (!this.canCopySelection) return false\n const path = this.store.ui.selection!\n const doc = this.store.doc!\n const box = this.boxByPath(path)\n if (!box) return false\n this.clipboardEntry = {\n layer: cloneLayerSubtree(resolveLayer(doc, path)!),\n sourceBox: box,\n sourcePath: path,\n }\n return true\n }\n\n /** 复制可用态(右键菜单「副本」的可用判定与 copySelection 同一语义) */\n get canCopySelection(): boolean {\n const path = this.store.ui.selection\n return path !== null && this.canCopyLayerAt(path)\n }\n\n /**\n * 路径处图层可复制(alt-drag-paste 工单 01,Alt+拖 copyMode 的绑定层判定面):\n * 可解析且类型可落根层(canCopyLayerAt 同门——行/格是容器内结构不可复制,\n * 格内容可复制)。无文档为 false。\n */\n canCopyLayerAt(path: LayerPath): boolean {\n const doc = this.store.doc\n return doc !== null && canCopyLayerAtPath(doc, path)\n }\n\n /**\n * 粘贴剪贴板(alt-drag-paste 工单 02 真原位):子树深拷贝插入根层——落点 =\n * 复制时点绝对盒、零偏移(重复粘贴同位叠放;锚点补偿保留:dx=0 时根层源\n * position 恒等原位、格内容源落其视觉位置);z 序紧邻源层(sourcePath 解析\n * 与退化置顶见 insertRootCopy);自动选中新层;一次调用 = 一步历史。空剪贴板\n * 为 null。每次粘贴从剪贴板快照重新克隆:粘贴产物之间以及与源文档都不共享\n * 引用。\n */\n pasteFromClipboard(): LayerPath | null {\n const entry = this.clipboardEntry\n const doc = this.store.doc\n if (!entry || !doc) return null\n const prepared = prepareRootPaste(entry.layer, entry.sourceBox, doc.width, doc.height, 0, 0)\n return this.insertRootCopy(prepared, entry.sourcePath)\n }\n\n /**\n * 创建副本 = 复制当前选中层 + 固定一格偏移(+20)+ 置顶 + 自动选中,一步历史。\n * 落位以当前绝对盒为基准(连续副本基于选中的副本链式偏移,天然不重叠)。\n * **不覆盖剪贴板**(先复制 A 再副本 B,粘贴仍出 A)。无选择/不可复制为 null。\n */\n duplicateSelection(): LayerPath | null {\n const path = this.store.ui.selection\n const doc = this.store.doc\n if (!path || !doc || !canCopyLayerAtPath(doc, path)) return null\n const box = this.boxByPath(path)\n if (!box) return null\n const prepared = prepareRootPaste(\n resolveLayer(doc, path)!,\n box,\n doc.width,\n doc.height,\n PASTE_OFFSET_PX,\n PASTE_OFFSET_PX,\n )\n return this.insertRootCopy(prepared)\n }\n\n /**\n * 粘贴/副本的公共落库尾:根层插入 + 自动选中新层;一次调用 = 一步历史。\n * source 给定时先试紧邻源层(源仍可解析且为根层——alt-drag-paste 工单 02 的\n * ⌘V 紧邻消费;路径寻址尽力而为:删源后同下标兄弟层/他文档同下标层会顶替\n * 解析,紧邻彼层是可接受的近似,工单 Comments 记档)——原语返回 null 或条件\n * 不满足回落置顶;source 缺省恒置顶(⌘D 语义不动)。\n */\n private insertRootCopy(prepared: Layer, source?: LayerPath): LayerPath | null {\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n index.v =\n source !== undefined && resolveLayer(draft, source) !== null && isRootLayerPath(source)\n ? insertRootLayerAdjacentInDraft(draft, prepared as Draft<Layer>, source) ?? -1\n : -1\n if (index.v < 0) index.v = insertRootLayerInDraft(draft, prepared as Draft<Layer>)\n })\n if (index.v < 0) return null\n const path: LayerPath = ['layers', index.v]\n this.store.setSelection(path)\n return path\n }\n\n // ---- 样式粘贴(canvas-web-style-paste 工单 01):样式集/适用面常量与快照/落地纯函数在 editing/styleClipboard ----\n\n /**\n * 复制样式(⌥⌘C / 右键菜单):按源类型适用面抓逐字段深拷贝快照进会话私有\n * 槽({sourceType, values})。源为**任意可解析路径**(根/行/格/格内容/模板\n * 子树皆可);无文档/路径不可解析/源无样式面(行模板替身,适用面为空)空转\n * 返回 false——空快照不占槽,已拷贝的样式不被无样式源清掉。拷贝不进历史\n * (剪贴板态不是文档态)。\n */\n copyStyle(path: LayerPath): boolean {\n const doc = this.store.doc\n const layer = doc ? resolveLayer(doc, path) : null\n if (!layer) return false\n const snapshot = captureStyleSnapshot(layer)\n if (!snapshot) return false\n this.styleClipboardEntry = snapshot\n return true\n }\n\n /** 复制可用态(右键菜单):有可解析选择且源类型有样式面 */\n get canCopyStyle(): boolean {\n const path = this.store.ui.selection\n const doc = this.store.doc\n if (path === null || doc === null) return false\n const layer = resolveLayer(doc, path)\n return layer !== null && STYLE_FIELD_KEYS_BY_TYPE[layer.type].length > 0\n }\n\n /**\n * 粘贴样式(⌥⌘V / 右键菜单):按目标类型适用面 ∩ 快照逐字段 verbatim 覆盖\n * (null 即值、Border 整对象;快照没有的字段 = 源不适型,静默跳过)。目标为\n * **任意可解析路径**——模板子树与格内容放行(尺寸字段不在样式集,行/格/\n * 内容宽高强同步天然不冲突);锁定语义天然放行(属性写通道,CONTEXT「锁定」\n * 词条)。单事务内循环写入 + autoHeight⟹height=0 断言 + canonicalizeTableSync\n * 一次收口(updateSpec 同门)——一次粘贴 = 一步历史,连续粘贴不合并(无\n * mergeKey);全等字段跳写、整事务零变化经 store 空 patch 短路不进历史;\n * 无文档/空槽/路径不可解析空转返回 false。\n */\n pasteStyle(path: LayerPath): boolean {\n const entry = this.styleClipboardEntry\n if (!entry || !this.store.doc) return false\n const written: TxOut<readonly (readonly string[])[]> = { v: [] }\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path) as Draft<Layer> | null\n if (!layer) return\n const keys = applyStyleFieldsInDraft(layer, entry)\n if (keys.length === 0) return\n // 解码不变量重断言(updateSpec 同门,一次收口):autoHeight⟹声明高归零\n // 经 renormalizeAutoHeightInDraft 收口;表格强同步按末写字段重断言——\n // 样式字段不触宽度耦合(根表分支天然空转),容器目标走内容/格/行\n // 重同步收口\n renormalizeAutoHeightInDraft(layer)\n canonicalizeTableSyncInDraft(draft, path, keys[keys.length - 1]!, this.textPolicies)\n written.v = keys\n })\n return written.v.length > 0\n }\n\n /** 粘贴可用态(右键菜单):样式槽非空且有可解析选择;交集为零变化的空转在动作面兜底 */\n get canPasteStyle(): boolean {\n const path = this.store.ui.selection\n const doc = this.store.doc\n return this.styleClipboardEntry !== null && path !== null && doc !== null && resolveLayer(doc, path) !== null\n }\n\n /**\n * z 序四件套(右键菜单语义 + kbd-nav 工单 01 键位):根层重排,v1 只作用根层\n * (行/格是数组序语义)。全部复用 moveRootLayer(priority 中点插值同款——\n * 置顶 = min−1、置底 = max+1、无整数间隙全表归一化兜底),一次调用 = 一步\n * 历史;已在目标端为无操作(不产生历史步)。返回是否发生移动(executeShortcut\n * 的可用态口径;右键菜单直调忽略返回值)。锁定层 z 序放行(锁定谓词的刻意\n * 通道,canvas-web-layer-lock 工单 01 同口径)。\n */\n bringToFront(): boolean {\n const panel = this.selectedRootPanelIndex()\n if (panel === null || panel === 0) return false\n this.moveRootLayer(panel, 0)\n return true\n }\n\n sendToBack(): boolean {\n const panel = this.selectedRootPanelIndex()\n const count = this.store.doc?.layers.length ?? 0\n if (panel === null || panel === count - 1) return false\n this.moveRootLayer(panel, count)\n return true\n }\n\n /**\n * 前移/后移一格(kbd-nav 工单 01):面板序号 ±1 的单格重排——与置顶/置底、\n * 面板拖动同一 moveRootLayer 语义(中点插值优先、无间隙全表归一化兜底),仅\n * 根层(isRoot 同门),已最前/最后空转。连按各成一步历史(无 mergeKey,\n * shortcut action 不合步的先例)。落点按「insert-before 原始序号」折算:\n * 前移 = 插到原 panel−1 层之前(to = panel−1),后移 = 插到原 panel+1 层之后\n * (to = panel+2)。\n */\n bringForward(): boolean {\n const panel = this.selectedRootPanelIndex()\n if (panel === null || panel <= 0) return false\n this.moveRootLayer(panel, panel - 1)\n return true\n }\n\n sendBackward(): boolean {\n const panel = this.selectedRootPanelIndex()\n const count = this.store.doc?.layers.length ?? 0\n if (panel === null || panel >= count - 1) return false\n this.moveRootLayer(panel, panel + 2)\n return true\n }\n\n /** 选中根层的面板序号(0 = 视觉最上层);非根层选择/无文档为 null */\n private selectedRootPanelIndex(): number | null {\n const path = this.store.ui.selection\n const doc = this.store.doc\n if (!path || !doc || !isRootLayerPath(path)) return null\n return doc.layers.length - 1 - (path[1] as number)\n }\n\n /**\n * 可分派的选中根层路径(⇧⌘L/⇧⌘H 分派口守卫,工单 02):无选择/无文档/非根\n * 形状/悬空(不可解析)一律 null——分派口「空转返回 false」与内核 action 的\n * 静默空转同口径(悬空窗口来自 prune 之外的结构步,兜底裁定可用态)。\n */\n private selectableRootPath(): LayerPath | null {\n const path = this.store.ui.selection\n const doc = this.store.doc\n if (path === null || doc === null || !isRootLayerPath(path)) return null\n return resolveLayer(doc, path) === null ? null : path\n }\n\n // ---- 快捷键分派(工单 14):注册表分类(shortcuts.ts)→ 这里执行 ----\n\n /**\n * 执行快捷键动作(注册表分类的出口):undo/redo/delete/copy/paste/duplicate/\n * copyStyle/pasteStyle(⌥⌘C/⌥⌘V,canvas-web-style-paste 工单 01)/\n * rename/findReplace(⌘F,canvas-web-find-replace 工单 01)/\n * toggleRulers/toggleLayerLock/toggleLayerVisibility 的统一分派面,以及\n * z 序四件套与缩放三件(kbd-nav 工单 01:bringForward/sendBackward/\n * bringToFront/sendToBack/zoomReset/fitToSurface/fitToSelection)、微调八动作\n * (kbd-nav 工单 02:nudgeUp/Down/Left/Right + Coarse 变体,归并到同一 nudge\n * 实现)与循环选层两动作(kbd-nav 工单 03:selectNextLayer/selectPrevLayer);\n * helpShortcuts(kbd-nav 工单 04)是 UI 面动作,由绑定层桥拦截路由\n * 帮助面板、不经此处。让路规则在\n * 分类器(classifyEditorShortcut)裁决,到达这里的动作不再重复判态;动作为\n * 空转(无选择/空剪贴板/非根层/已在端点)返回 false,其余 true。rename 开\n * 选中根层的重命名会话(F2,工单 09),不直接写文档;锁定/显隐作用于选中根层\n * (canvas-web-layer-lock 工单 02 + feature-status 显隐键位挂账补位),非根/\n * 无选择按可用态裁剪返回 false;相机三件不进历史、恒 true(surface 未知时\n * 内核静默空转,同 toggleRulers 的无条件口径)。\n */\n executeShortcut(action: EditorShortcutAction): boolean {\n switch (action) {\n case 'undo':\n this.undo()\n return true\n case 'redo':\n this.redo()\n return true\n case 'copy':\n return this.copySelection()\n case 'paste':\n return this.pasteFromClipboard() !== null\n case 'duplicate':\n return this.duplicateSelection() !== null\n case 'copyStyle': {\n // 样式粘贴(canvas-web-style-paste 工单 01):⌥⌘C 分派口,无选择空转\n const path = this.store.ui.selection\n return path !== null && this.copyStyle(path)\n }\n case 'pasteStyle': {\n // ⌥⌘V 分派口:空槽/无选择空转(可用态口径与右键菜单 canPasteStyle 同源)\n const path = this.store.ui.selection\n return path !== null && this.pasteStyle(path)\n }\n case 'rename':\n return this.beginRename(this.store.ui.selection)\n case 'findReplace':\n // 查找替换(canvas-web-find-replace 工单 01):⌘F 分派 beginFind——\n // 面板开合语义内核只持会话态(Esc 关归 Vue 面板组件,工单 03 接线)\n return this.beginFind()\n case 'armCreateText':\n case 'armCreateTable':\n case 'armCreateQrCode':\n case 'armCreateImage':\n // 画拉建层武装四条目(canvas-web-drag-create 工单 03):T/G/Q/I 分派\n // 统一武装口——面板新增项与层型快捷键同缝(spec 决策 1);武装只置\n // ui 待命态,落库在画拉 endCreate(此处恒 true,状态栏提示归绑定层桥;\n // 动作→层型映射与绑定层共用 ARM_CREATE_LAYER_TYPES 单一事实源)\n this.armLayerCreate(ARM_CREATE_LAYER_TYPES[action])\n return true\n case 'toggleRulers':\n this.toggleRulers()\n return true\n case 'toggleLayerLock': {\n const path = this.selectableRootPath()\n if (path === null) return false\n this.toggleLayerLock(path)\n return true\n }\n case 'toggleLayerVisibility': {\n const path = this.selectableRootPath()\n if (path === null) return false\n this.toggleLayerVisibility(path)\n return true\n }\n case 'bringForward':\n return this.bringForward()\n case 'sendBackward':\n return this.sendBackward()\n case 'bringToFront':\n return this.bringToFront()\n case 'sendToBack':\n return this.sendToBack()\n case 'nudgeUp':\n return this.nudge(0, -NUDGE_STEP_PX)\n case 'nudgeDown':\n return this.nudge(0, NUDGE_STEP_PX)\n case 'nudgeLeft':\n return this.nudge(-NUDGE_STEP_PX, 0)\n case 'nudgeRight':\n return this.nudge(NUDGE_STEP_PX, 0)\n case 'nudgeUpCoarse':\n return this.nudge(0, -NUDGE_COARSE_STEP_PX)\n case 'nudgeDownCoarse':\n return this.nudge(0, NUDGE_COARSE_STEP_PX)\n case 'nudgeLeftCoarse':\n return this.nudge(-NUDGE_COARSE_STEP_PX, 0)\n case 'nudgeRightCoarse':\n return this.nudge(NUDGE_COARSE_STEP_PX, 0)\n case 'selectNextLayer':\n return this.selectNextLayer()\n case 'selectPrevLayer':\n return this.selectPrevLayer()\n case 'zoomReset':\n this.resetZoom()\n return true\n case 'fitToSurface':\n this.fitToSurface()\n return true\n case 'fitToSelection':\n this.fitToSelection()\n return true\n case 'helpShortcuts':\n // UI 面动作(kbd-nav 工单 04):绑定层桥拦截路由到帮助面板\n // (useShortcutsHelp),不会到达这里;case 仅为动作联合穷尽\n // (TS),防御性按不可用态口径返回 false\n return false\n case 'delete': {\n const path = this.store.ui.selection\n if (!path) return false\n this.deleteLayer(path)\n return true\n }\n }\n }\n\n // ---- 表格容器结构编辑(工单 12):重建路径与 graph 解码同一套 add 同步语义 ----\n\n /**\n * 新增表格行(面板「加行」入口):缺省行 + 缺省格(带文本内容)走重建路径\n * (行宽=表宽、内容宽=格宽、固定格压平内容高、行高取最高格——语义见\n * tableEditing.addTableRowInDraft),并自动选中新行;一次调用 = 一步历史。\n */\n addTableRow(tablePath: LayerPath): void {\n if (!this.store.doc) return\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n index.v = addTableRowInDraft(draft, tablePath) ?? -1\n })\n if (index.v >= 0) this.store.setSelection([...tablePath, 'rows', index.v])\n }\n\n /**\n * 新增表格单元格(面板「加格」入口):缺省格 + 缺省文本内容走重建路径\n * (内容宽=格宽、固定格压平内容高、行高取最高格),并自动选中新格;\n * 一次调用 = 一步历史。\n */\n addTableCell(rowPath: LayerPath): void {\n if (!this.store.doc) return\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n index.v = addTableCellInDraft(draft, rowPath) ?? -1\n })\n if (index.v >= 0) this.store.setSelection([...rowPath, 'cells', index.v])\n }\n\n /**\n * 新增模板表(「+」选择器入口,spec §2.1):createTemplateTable 缺省形态 +\n * 置顶 + 自动选中;一次调用 = 一步历史。rowsPath 必填(解码 rows_path_missing\n * 硬约束的对齐兜底,表单校验前置),空串 no-op。\n */\n addTemplateTable(rowsPath: string): void {\n if (!this.store.doc || rowsPath === '') return\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n index.v = insertRootLayerInDraft(draft, createTemplateTable({ rowsPath }) as Draft<Layer>)\n })\n if (index.v >= 0) this.store.setSelection(['layers', index.v])\n }\n\n /**\n * 模板行加格(spec §3.1):addTemplateCellInDraft 重建路径(末格宽/首格行宽、\n * 零高度耦合),自动选中新格;一次调用 = 一步历史。非模板态 no-op。\n */\n addTemplateCell(tablePath: LayerPath): void {\n if (!this.store.doc) return\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n index.v = addTemplateCellInDraft(draft, tablePath) ?? -1\n })\n if (index.v >= 0) this.store.setSelection([...tablePath, 'template', 'cells', index.v])\n }\n\n /**\n * V1→V2 转换(spec §2.3):rowsPath 必填(空串 no-op,表单校验前置);rows\n * 子树内的选择/悬停回落表路径(rows 已清空不悬空)。一次调用 = 一步历史。\n */\n convertTableToTemplate(tablePath: LayerPath, rowsPath: string): void {\n if (!this.store.doc || rowsPath === '') return\n const done: TxOut<boolean> = { v: false }\n this.store.transact((draft) => {\n done.v = convertTableToTemplateInDraft(draft, tablePath, rowsPath) === true\n })\n if (done.v) this.clampSubtreeSelection(tablePath)\n }\n\n /**\n * V2→V1 逆向转换(spec §2.3):模板实例化为单行(V1 归一重断言——auto 格\n * 采纳/固化、行高取最高格);模板子树内的选择/悬停回落表路径。一次调用 =\n * 一步历史。\n */\n convertTableToRows(tablePath: LayerPath): void {\n if (!this.store.doc) return\n const done: TxOut<boolean> = { v: false }\n this.store.transact((draft) => {\n done.v = convertTableToRowsInDraft(draft, tablePath, this.textPolicies) === true\n })\n if (done.v) this.clampSubtreeSelection(tablePath)\n }\n\n /** 子树内选择/悬停回落到子树根(容器清空后不悬空);子树外原样不动 */\n private clampSubtreeSelection(rootPath: LayerPath): void {\n const selection = this.store.ui.selection\n if (selection !== null && selection.length > rootPath.length && pathStartsWith(selection, rootPath)) {\n this.store.setSelection(rootPath)\n }\n const hovered = this.store.ui.hovered\n if (hovered !== null && hovered.length > rootPath.length && pathStartsWith(hovered, rootPath)) {\n this.store.setHovered(rootPath)\n }\n }\n\n /**\n * 单元格移动:落点为「insert-before 原始序号」的目标行 + 序号。同行退化为\n * 数组序重排(与行重排同款 moveGuard 语义),跨行走重建路径(目标行高取\n * 最高格、内容层归属随格重同步)。一次调用 = 一步历史。\n */\n moveTableCellToRow(cellPath: LayerPath, targetRowPath: LayerPath, toCell: number): void {\n if (!this.store.doc) return\n const sourceRowPath = cellPath.slice(0, -2)\n if (pathsEqual(sourceRowPath, targetRowPath)) {\n const from = cellPath[cellPath.length - 1]\n if (typeof from === 'number') this.moveTableCell(sourceRowPath, from, toCell)\n return\n }\n const moved: TxOut<MovedSubtreeRef | null> = { v: null }\n this.store.transact((draft) => {\n moved.v = moveTableCellToRowInDraft(draft, cellPath, targetRowPath, toCell, this.textPolicies)\n })\n if (moved.v !== null) this.remapMovedSubtree(moved.v)\n }\n\n /** 同行单元格重排(直接改 cells 数组序,容器内嵌套数组序语义) */\n moveTableCell(rowPath: LayerPath, fromCell: number, toCell: number): void {\n if (!this.store.doc) return\n const moved: TxOut<{ from: number; to: number } | null> = { v: null }\n this.store.transact((draft) => {\n moved.v = moveTableCellInDraft(draft, rowPath, fromCell, toCell)\n })\n if (moved.v === null) return\n this.remapPathSlices(rowPath, 'cells', moved.v.from, moved.v.to)\n }\n\n /**\n * 行移动:落点为「insert-before 原始序号」的目标表 + 序号。同表退化为数组序\n * 重排(moveTableRow),跨表走重建路径(行宽同步目标表宽——addRow 副作用)。\n * 一次调用 = 一步历史。\n */\n moveTableRowToTable(rowPath: LayerPath, targetTablePath: LayerPath, toRow: number): void {\n if (!this.store.doc) return\n const sourceTablePath = rowPath.slice(0, -2)\n if (pathsEqual(sourceTablePath, targetTablePath)) {\n const from = rowPath[rowPath.length - 1]\n if (typeof from === 'number') this.moveTableRow(sourceTablePath, from, toRow)\n return\n }\n const moved: TxOut<MovedSubtreeRef | null> = { v: null }\n this.store.transact((draft) => {\n moved.v = moveTableRowToTableInDraft(draft, rowPath, targetTablePath, toRow)\n })\n if (moved.v !== null) this.remapMovedSubtree(moved.v)\n }\n\n /**\n * 格 autoHeight 切换:开且带内容 = 采纳内容动态高(autowrap 文本即\n * 「行数×行高+padding」)并固化(解码对带内容的格恒归一为固定高);开且空格 =\n * 置标志;关 = 清标志。采纳可能增高,行高随之取最高格。一次调用 = 一步历史。\n */\n setCellAutoHeight(cellPath: LayerPath, on: boolean): void {\n if (!this.store.doc) return\n const applied: TxOut<boolean> = { v: false }\n this.store.transact((draft) => {\n applied.v = setCellAutoHeightInDraft(draft, cellPath, on, this.textPolicies)\n })\n if (applied.v) this.pruneDanglingPaths()\n }\n\n // ---- 撤销/重做(工单 08):双栈语义全在 store,会话只透传查询与动作 ----\n\n get canUndo(): boolean {\n return this.store.canUndo\n }\n\n get canRedo(): boolean {\n return this.store.canRedo\n }\n\n undo(): void {\n this.store.undo()\n // 历史回放不携带 ui 路径态:撤销后选择/悬停可能指向已消失的图层(如撤销\n // 新增行),按「路径可解析」清理悬空态(工单 12 选择协同)\n this.pruneDanglingPaths()\n }\n\n redo(): void {\n this.store.redo()\n this.pruneDanglingPaths()\n }\n\n // ---- 订阅与失效 ----\n\n subscribe(listener: (change: EditorChange) => void): () => void {\n return this.store.subscribe(listener)\n }\n\n /** 外部脏源入口(如资源物化完成 → 'content') */\n invalidate(target: InvalidateTarget = 'both'): void {\n if (target === 'content') this.dirty.content = true\n else if (target === 'overlay') this.dirty.overlay = true\n else {\n this.dirty.content = true\n this.dirty.overlay = true\n }\n this.requestFrame()\n }\n\n /**\n * 挂起帧同步排空(canvas-web-render-perf 工单 01):绑定层在「物理缓冲已被清空、\n * 需要背靠背补绘」的时机调用(CanvasSurface 的 resizeBuffers 赋 canvas.width 清屏\n * 后——ResizeObserver 回调在本帧 rAF 之后,等下帧重绘必现一帧空白)。有挂起帧即\n * 取消排定并按当前脏标立即 flush;无挂起帧空转幂等。内核保持无 DOM:只操作既有\n * cancelFrame/flush,不感知帧源(rAF 语义归绑定层注入的调度器)。\n */\n flushPendingFrames(): void {\n if (!this.frameQueued) return\n // 先摘排定再同步 flush(被取消的帧回调不得在排空后再次触发);标志复位\n // 归 flush(),此处不重置\n this.cancelFrame?.()\n this.flush()\n }\n\n /** 取消挂起帧、退订 store、解绑绘制面 */\n dispose(): void {\n if (this.cancelFrame) this.cancelFrame()\n this.cancelFrame = null\n this.frameQueued = false\n this.unsubscribeStore()\n this.detachSurfaces()\n }\n\n // ---- 内部:合帧与重绘 ----\n\n /**\n * 结构变更后重映射 ui 分支的路径态(选择/悬停/重命名会话/锁定集合):路径是\n * 数组下标身份,容器 splice 后按 remapPathAfterSplice 平移,被移出子树的路径\n * 落地为清除。ui 整体替换、不进历史;值未变的路径 set* 内部值等短路。\n * 锁定集合(canvas-web-layer-lock 工单 01)作为集合平移——锁「胶在层上」走过\n * splice(兄弟增删/根层重排后锁仍指原层),被移出容器的锁定路径落地解锁。\n */\n private remapPathSlices(\n containerPath: LayerPath,\n key: 'layers' | 'rows' | 'cells',\n from: number,\n to: number,\n ): void {\n const remap = (path: LayerPath | null): LayerPath | null =>\n path === null ? null : remapPathAfterSplice(path, containerPath, key, from, to)\n const selection = remap(this.store.ui.selection)\n if (selection !== this.store.ui.selection) this.store.setSelection(selection)\n const hovered = remap(this.store.ui.hovered)\n if (hovered !== this.store.ui.hovered) this.store.setHovered(hovered)\n const renaming = remap(this.store.ui.renaming)\n if (renaming !== this.store.ui.renaming) this.store.setRenaming(renaming)\n this.store.setLockedPaths(this.remappedLocks(this.store.ui.lockedPaths, remap))\n }\n\n /**\n * 锁定集合经映射平移(canvas-web-layer-lock 工单 01):逐路径过 map、滤除\n * 落地 null 的悬空路径;集合未变时 setLockedPaths 内容等短路(无锁/未变\n * 不通知)。remapPathSlices 的 splice 平移与 remapMovedSubtree 的前缀重挂\n * 共用同一形状——后者必须传移动前捕获的集合(源 splice 步已丢掉移动子树\n * 内的锁)。\n */\n private remappedLocks(\n paths: readonly LayerPath[],\n map: (path: LayerPath) => LayerPath | null,\n ): LayerPath[] {\n return paths.reduce<LayerPath[]>((kept, locked) => {\n const mapped = map(locked)\n if (mapped !== null) kept.push(mapped)\n return kept\n }, [])\n }\n\n /**\n * 跨容器移动后的选择/悬停/锁定集合重映射(工单 12 + canvas-web-layer-lock\n * 工单 01):先做源容器纯删除与目标容器纯插入的索引平移(移动子树内的路径\n * 在源删除步被置 null,不参与平移),再把移动前落在移动子树内的路径按前缀\n * 重挂到新位置(子树内部相对结构不变,仅容器段整体替换)。锁定集合的重挂\n * 当前是不变量兜底——锁定恒为根层路径、跨容器移动的子树恒为行/格子树,两集\n * 天然不相交;将来锁面扩展或根层跨容器移动接入时语义已在。\n */\n private remapMovedSubtree(moved: MovedSubtreeRef): void {\n const selectionBefore = this.store.ui.selection\n const hoveredBefore = this.store.ui.hovered\n const lockedBefore = this.store.ui.lockedPaths\n this.remapPathSlices(moved.source.containerPath, moved.source.key, moved.source.from, moved.source.to)\n this.remapPathSlices(moved.target.containerPath, moved.target.key, moved.target.from, moved.target.to)\n const rebase = (before: LayerPath | null): LayerPath | null => {\n if (before === null || !pathStartsWith(before, moved.fromPath)) return null\n return [...moved.toPath, ...before.slice(moved.fromPath.length)] as LayerPath\n }\n const selection = rebase(selectionBefore)\n if (selection !== null && !pathsEqual(this.store.ui.selection, selection)) {\n this.store.setSelection(selection)\n }\n const hovered = rebase(hoveredBefore)\n if (hovered !== null && !pathsEqual(this.store.ui.hovered, hovered)) {\n this.store.setHovered(hovered)\n }\n const rebasedLocks = this.remappedLocks(lockedBefore, rebase)\n if (rebasedLocks.length > 0) {\n this.store.setLockedPaths([...this.store.ui.lockedPaths, ...rebasedLocks])\n }\n }\n\n /**\n * 悬空路径清理:选择/悬停/重命名会话指向已不存在的图层即清空(撤销/重做与\n * 采纳类收口共用)。锁定集合同门(canvas-web-layer-lock 工单 01):锁定路径\n * 悬空即解锁。已知限制(spec §3 记档不修):undo/redo 只有 prune 无平移——\n * 跨结构步撤销后锁可能错位(路径仍可解析、指到别的层)或清失,锁定钮常显\n * 可目视纠正;wire 持久化(锁长在层对象上)根治。\n */\n private pruneDanglingPaths(): void {\n const doc = this.store.doc\n if (!doc) return\n const { selection, hovered, renaming } = this.store.ui\n if (selection !== null && resolveLayer(doc, selection) === null) this.store.setSelection(null)\n if (hovered !== null && resolveLayer(doc, hovered) === null) this.store.setHovered(null)\n if (renaming !== null && resolveLayer(doc, renaming) === null) this.store.setRenaming(null)\n this.store.setLockedPaths(\n this.store.ui.lockedPaths.filter((locked) => resolveLayer(doc, locked) !== null),\n )\n }\n\n private onStoreChange(change: EditorChange): void {\n if (change.scope === 'doc') this.invalidate('both')\n else if (change.branch === 'viewport') this.invalidate('both')\n // 编辑会话开始/结束切换内容层的文本跳绘(textarea 接管该层呈现),双层都要重绘\n else if (change.branch === 'editing') this.invalidate('both')\n // 资源物化状态(placeholder-padding-hint 工单 02):装载落定伴随内容像素\n // 变化(完成出图/失败占位),提示面在绑定层读 ui 切片——双层重绘\n else if (change.branch === 'resourceStatuses') this.invalidate('both')\n // schema 声明不触达像素(候选消费在绑定层补全面),不参与重绘脏标\n else if (change.branch === 'dataSourceSchema') return\n // 选择/悬停/拖动会话/重命名/锁定集合/查找会话(命中高亮随 query/游标变)\n // 只影响 gizmo(锁定不改渲染产物——命中面是事件侧语义;拖动中的图层位移\n // 走 doc 分支另触发双层)\n else this.invalidate('overlay')\n }\n\n private requestFrame(): void {\n if (this.frameQueued) return\n this.frameQueued = true\n this.cancelFrame = this.scheduleFrame(() => this.flush())\n }\n\n private flush(): void {\n this.frameQueued = false\n this.cancelFrame = null\n const { content, overlay } = this.dirty\n this.dirty = { content: false, overlay: false }\n if (content) this.repaintContent()\n if (overlay) this.repaintOverlay()\n }\n\n private presentedViewport(): Viewport {\n return snapViewportToPhysicalPixels(this.store.ui.viewport, this.dpr)\n }\n\n private repaintContent(): void {\n const doc = this.store.doc\n const view = this.previewCanvas\n if (!doc || !view || !this.backend) return\n const viewport = this.presentedViewport()\n const transform: PreviewViewportTransform = {\n dpr: this.dpr,\n zoom: viewport.zoom,\n x: viewport.x,\n y: viewport.y,\n }\n // 视口变换是 Canvas2D 后端的可选能力;后端契约本身保持五原语不变\n ;(this.backend as Partial<ViewportAwareBackend>).setViewportTransform?.(transform)\n // 编辑中的文本层跳绘内容(textarea 接管该层文字呈现,防两侧断行叠加重影);\n // 盒(背景/边框)照常绘制,进出编辑视觉连续。跳绘判定按预览视图中的图层\n // 引用:模板子树在视图里是新实例,经路径映射取视图侧对象才能命中同一引用\n // (非模板子树结构共享,两棵树同引用)。\n const editing = this.store.ui.editing\n let editingLayer: Layer | null = null\n if (editing) {\n const viewPath = docPathToViewPath(doc, editing.path)\n editingLayer = viewPath ? resolveLayer(view, viewPath as LayerPath) : null\n }\n renderCanvas(view, this.backend, this.textPolicies, editingLayer\n ? { skipContent: (layer) => layer === editingLayer }\n : undefined)\n }\n\n private repaintOverlay(): void {\n if (!this.overlayPainter) return\n this.overlayPainter({\n viewport: this.presentedViewport(),\n dpr: this.dpr,\n doc: this.store.doc,\n })\n }\n}\n","/**\n * 相机纯函数(工单 05)。统一坐标定义(impl 研究文档 §2.6):\n *\n * screen = (scene - cam) * zoom\n *\n - 场景坐标全程保持画布整数像素语义(与后端整型口径一致),换算只发生在\n * 输入端(指针 → 场景)与呈现端(ctx transform);本模块只做数值换算,\n * 不触及任何 DOM/渲染对象。\n * - 相机平移可对齐物理像素(snapViewportToPhysicalPixels),避免缩放/平移\n * 时的亚像素抖动;zoom 数值本身不取整,缩放百分比显示用原值。\n */\n\n/** 视口:相机(场景坐标下的视口左上角)+ 缩放倍率 */\nexport interface Viewport {\n x: number\n y: number\n zoom: number\n}\n\nexport interface Point {\n x: number\n y: number\n}\n\nexport interface Size {\n width: number\n height: number\n}\n\n/** 场景矩形(适应选区的目标盒等) */\nexport interface Rect {\n x: number\n y: number\n width: number\n height: number\n}\n\n/** 缩放范围(可配置;缺省 5%–800%,工单 05 拍板) */\nexport interface ZoomBounds {\n min: number\n max: number\n}\n\nexport const DEFAULT_ZOOM_BOUNDS: ZoomBounds = { min: 0.05, max: 8 }\n\n/** 场景 → 屏幕(呈现端换算) */\nexport function sceneToScreen(viewport: Viewport, sceneX: number, sceneY: number): Point {\n return {\n x: (sceneX - viewport.x) * viewport.zoom,\n y: (sceneY - viewport.y) * viewport.zoom,\n }\n}\n\n/** 屏幕 → 场景(输入端换算:命中测试等) */\nexport function screenToScene(viewport: Viewport, screenX: number, screenY: number): Point {\n return {\n x: screenX / viewport.zoom + viewport.x,\n y: screenY / viewport.zoom + viewport.y,\n }\n}\n\n/** 缩放倍率钳位到范围 */\nexport function clampZoom(zoom: number, bounds: ZoomBounds): number {\n return Math.min(bounds.max, Math.max(bounds.min, zoom))\n}\n\n/**\n * 平移:屏幕位移按 zoom 折算回场景位移(cam -= delta / zoom),\n * 保证平移速度与缩放倍率无关。\n */\nexport function panBy(viewport: Viewport, deltaScreenX: number, deltaScreenY: number): Viewport {\n return {\n x: viewport.x - deltaScreenX / viewport.zoom,\n y: viewport.y - deltaScreenY / viewport.zoom,\n zoom: viewport.zoom,\n }\n}\n\n/**\n * 缩放至指针(zoom-to-pointer):保持指针下的场景点不动——\n * scene₀ = screen₀ / zoom + cam ⇒ cam' = scene₀ - screen₀ / zoom'\n * 目标倍率先钳位到范围,锚点关系按钳位后的倍率成立。\n */\nexport function zoomAtPoint(\n viewport: Viewport,\n screenX: number,\n screenY: number,\n nextZoom: number,\n bounds: ZoomBounds,\n): Viewport {\n const zoom = clampZoom(nextZoom, bounds)\n if (zoom === viewport.zoom) return viewport\n return {\n x: screenX / viewport.zoom + viewport.x - screenX / zoom,\n y: screenY / viewport.zoom + viewport.y - screenY / zoom,\n zoom,\n }\n}\n\n/**\n * 适应画布:整页可见(宽高取更紧一侧)、画布中心对齐视口中心。\n * margin 为可视区域内缩(屏幕像素)。缩放范围只钳**上界**(防小画布被荒谬放大),\n * 不钳下界——「适应」的语义是整页可见,超大画布允许低于交互缩放下界。\n * 零尺寸输入防御回落恒等视口。\n */\nexport function fitViewport(\n canvas: Size,\n surface: Size,\n bounds: ZoomBounds,\n margin: number,\n): Viewport {\n return fitRect({ x: 0, y: 0, width: canvas.width, height: canvas.height }, surface, bounds, margin)\n}\n\n/** 适应任意场景矩形(工单 06 适应选区):矩形整块可见、矩形中心对齐视口中心 */\nexport function fitRect(\n rect: Rect,\n surface: Size,\n bounds: ZoomBounds,\n margin: number,\n): Viewport {\n const innerW = surface.width - margin * 2\n const innerH = surface.height - margin * 2\n if (rect.width <= 0 || rect.height <= 0 || innerW <= 0 || innerH <= 0) {\n return { x: 0, y: 0, zoom: 1 }\n }\n const zoom = Math.min(bounds.max, Math.min(innerW / rect.width, innerH / rect.height))\n return {\n // 矩形中心 (rect.cx, rect.cy) 落在视口中心 (surface/2):cam = 中心 - 视口中心 / zoom\n x: rect.x + rect.width / 2 - surface.width / 2 / zoom,\n y: rect.y + rect.height / 2 - surface.height / 2 / zoom,\n zoom,\n }\n}\n\n/**\n * 保 zoom 平移入视的小边距(屏幕 px,canvas-web-find-replace 工单 02 定死):\n * 出视入视后盒缘与视口缘的留白。\n */\nexport const PAN_TO_BOX_MARGIN_PX = 12\n\n/**\n * 保 zoom 平移入视(canvas-web-find-replace 工单 02,spec 决策 6):查找导航的\n * 视口跟随语义,与 fitRect 的变焦语义分立——缩放倍率恒不变,只在目标盒出视时\n * 最小平移使整盒入视。\n *\n * - 盒完全在视口内(贴边也算)→ 原视口原样返回(引用相等,调用方可据此短路通知);\n * - 出视才平移,两轴独立求位,入视带含 margin 留白(屏幕 px):\n * - 盒装得下「视口 − 2×margin」:cam 钳进可行区间 = 各轴最小平移(非居中);\n * - 盒装得下视口但 margin 两侧放不下:margin 让位,钳进裸视口可行区间 = 贴缘\n * 最小平移(整盒可见优先于边距,spec「最小平移」字面口径);\n * - 盒大于视口:对齐盒左上(左上角落在 margin 处,右/下侧自然截断——工单\n * 定死取「阅读起点可见」而非居中)。\n * 零尺寸表面/非正 zoom 防御回落原视口。\n */\nexport function panToBox(viewport: Viewport, box: Rect, surface: Size, margin: number): Viewport {\n if (surface.width <= 0 || surface.height <= 0 || !(viewport.zoom > 0)) return viewport\n const panAxis = (boxPos: number, boxSize: number, surfaceSize: number, cam: number): number => {\n const left = (boxPos - cam) * viewport.zoom\n const right = left + boxSize * viewport.zoom\n if (left >= 0 && right <= surfaceSize) return cam\n const slack = surfaceSize - boxSize * viewport.zoom\n if (slack >= margin * 2) {\n // 可行区间 [lo, hi]:cam 取区间内任意值盒都整在 [margin, surface − margin]\n // 入视带内;钳进最近端 = 最小平移\n const lo = boxPos + boxSize - (surfaceSize - margin) / viewport.zoom\n const hi = boxPos - margin / viewport.zoom\n return Math.min(hi, Math.max(lo, cam))\n }\n if (slack >= 0) {\n // margin 带放不下整盒(盒贴近视口尺寸):margin 让位,裸视口可行区间\n // 钳近端 = 贴缘最小平移,盒从出视一侧探入\n const lo = boxPos + boxSize - surfaceSize / viewport.zoom\n const hi = boxPos\n return Math.min(hi, Math.max(lo, cam))\n }\n return boxPos - margin / viewport.zoom\n }\n const x = panAxis(box.x, box.width, surface.width, viewport.x)\n const y = panAxis(box.y, box.height, surface.height, viewport.y)\n if (x === viewport.x && y === viewport.y) return viewport\n return { x, y, zoom: viewport.zoom }\n}\n\n/**\n * 相机平移对齐物理像素:cam = round(cam * zoom * dpr) / (zoom * dpr)。\n * 只动 x/y,zoom 保持原值(百分比显示用原值)。\n */\nexport function snapViewportToPhysicalPixels(viewport: Viewport, dpr: number): Viewport {\n if (!(dpr > 0)) return viewport\n const gridX = viewport.zoom * dpr\n return {\n x: Math.round(viewport.x * gridX) / gridX,\n y: Math.round(viewport.y * gridX) / gridX,\n zoom: viewport.zoom,\n }\n}\n\n/**\n * 滚轮/触控板捏合的下一档缩放(excalidraw App.wheel.ts 同式,加性曲线):\n * - 单档步长 ZOOM_STEP,delta 钳位到 ±ZOOM_STEP*100(防大 delta 过冲);\n * - >100% 区按 log10(zoom) 增幅(越放大步长越大);\n * - 增幅项按 min(1, |Δ|/20) 衰减,防触控板小位移微抖。\n * deltaY > 0(向下滚)= 缩小。结果钳位到范围。\n */\nexport const WHEEL_ZOOM_STEP = 0.1\n\nexport function nextZoomByWheel(zoom: number, deltaY: number, bounds: ZoomBounds): number {\n if (deltaY === 0) return clampZoom(zoom, bounds)\n const sign = Math.sign(deltaY)\n const absDelta = Math.abs(deltaY)\n const delta = Math.min(absDelta, WHEEL_ZOOM_STEP * 100) * sign\n\n let next = zoom - delta / 100\n next += Math.log10(Math.max(1, zoom)) * -sign * Math.min(1, absDelta / 20)\n return clampZoom(next, bounds)\n}\n","/**\n * 命中测试纯函数(工单 06,spec「渲染与交互」):\n * 从视觉最上层向下命中——图层数组尾(后画在上)→ 头线性遍历 + 矩形包含,\n * 表格递归下钻(格内容 → 格 → 行 → 表)。盒几何与渲染模板共用\n * (resolveLayerBox + resolveChildAt,与 walkLayer 同一套推进公式),\n * 负溢出(盒出画布)照常包含,不做空间索引(图元量级小)。\n */\nimport { resolveChildAt, resolveLayerBox, type Canvas, type Layer, type LayerBox, type TextLayoutPolicies } from '@hankchen/canvas'\n\nimport { isLockedPath, type LayerPath } from '../shared/layerPath'\n\n/** 半开区间包含 [x, x+w) × [y, y+h):相邻格边界恰好归一格,零尺寸不可命中 */\nfunction contains(box: LayerBox, x: number, y: number): boolean {\n return box.width > 0\n && box.height > 0\n && x >= box.x\n && x < box.x + box.width\n && y >= box.y\n && y < box.y + box.height\n}\n\n/** 命中选项:过滤集合由调用方注入(内核 ui 分支只读投影,纯函数不自取状态) */\nexport interface HitTestOptions {\n /**\n * 锁定根层路径集合(canvas-web-layer-lock 工单 01):锁定的根层整子树退出\n * 命中面、命中穿透下方层,与 visible 过滤同门;与底图豁免叠加时锁优先\n * (豁免只跳自身盒,锁整子树退出)。\n */\n readonly lockedPaths?: readonly LayerPath[]\n}\n\n/**\n * 全幅判定:底层自身盒 ⊇ 画布矩形(负溢出照算),与视口/缩放无关。\n * spec「渲染与交互」2026-09-27 拍板的底图豁免(Fabric backgroundImage 不入命中图、\n * Konva listening(false) 同构):只作用于数组头根层——「数组按 priority 降序、\n * 头先画垫底」的叠放不变量决定了它是唯一的视觉垫底层。\n */\nfunction coversCanvas(box: LayerBox, canvasWidth: number, canvasHeight: number): boolean {\n return box.x <= 0\n && box.y <= 0\n && box.x + box.width >= canvasWidth\n && box.y + box.height >= canvasHeight\n}\n\nfunction hitWalk(\n layer: Layer,\n path: LayerPath,\n box: LayerBox,\n x: number,\n y: number,\n policies?: TextLayoutPolicies,\n skipOwnBox = false,\n): LayerPath | null {\n // 子层后画在上:先下钻(尾→头),都未中再试自身盒(次序不动——豁免只跳自身盒)\n switch (layer.type) {\n case 'TableLayer':\n for (let i = layer.rows.length - 1; i >= 0; i -= 1) {\n const child = resolveChildAt(layer, box, 'rows', i, policies)\n if (!child) continue\n const hit = hitWalk(child.layer, [...path, 'rows', i], child.box, x, y, policies)\n if (hit) return hit\n }\n break\n case 'TableRowLayer':\n for (let i = layer.cells.length - 1; i >= 0; i -= 1) {\n const child = resolveChildAt(layer, box, 'cells', i, policies)\n if (!child) continue\n const hit = hitWalk(child.layer, [...path, 'cells', i], child.box, x, y, policies)\n if (hit) return hit\n }\n break\n case 'TableCellLayer':\n // 内容层与单元格同原点,最上优先\n if (layer.content) {\n const child = resolveChildAt(layer, box, 'content', 0, policies)\n if (child) {\n const hit = hitWalk(child.layer, [...path, 'content'], child.box, x, y, policies)\n if (hit) return hit\n }\n }\n break\n case 'ImageLayer':\n case 'TextLayer':\n case 'QrCodeLayer':\n case 'TableRowTemplate':\n // 行模板子树不在 rows/cells/content 内,命中面天然为空(工票 02 穷举补臂)\n break\n }\n\n return !skipOwnBox && contains(box, x, y) ? path : null\n}\n\n/**\n * 场景坐标命中:返回视觉最上被中图层的路径(未中返回 null)。\n * 隐藏根层(layer-panel-ux 工单 10)与锁定根层(canvas-web-layer-lock 工单 01)\n * 整子树退出命中面——渲染端整层跳过 / 锁定层只退交互面(渲染照常),不可见/\n * 被锁定即不可点选,命中穿透其下方的层。\n */\nexport function hitTest(\n canvas: Canvas,\n sceneX: number,\n sceneY: number,\n policies?: TextLayoutPolicies,\n options?: HitTestOptions,\n): LayerPath | null {\n const lockedPaths = options?.lockedPaths\n for (let i = canvas.layers.length - 1; i >= 0; i -= 1) {\n const layer = canvas.layers[i]!\n if (layer.visible === false) continue\n const rootPath: LayerPath = ['layers', i]\n if (lockedPaths !== undefined && isLockedPath(rootPath, lockedPaths)) continue\n const box = resolveLayerBox(layer, 0, 0, canvas.width, canvas.height, policies)\n // 全幅底层豁免只跳过自身盒测试(不能对整层 continue):全幅底表的行/格/格内容照常可命中\n const skipOwnBox = i === 0 && coversCanvas(box, canvas.width, canvas.height)\n const hit = hitWalk(layer, rootPath, box, sceneX, sceneY, policies, skipOwnBox)\n if (hit) return hit\n }\n return null\n}\n","/**\n * 选择身份 = 图层路径(工单 06)。\n *\n * graph 无 id 字段且往返恒等契约禁止私加(spec「内核(store 与历史)」),\n * 选中身份用「从画布根到图层的数组路径」表达,语法即 immer patch path 前缀:\n *\n * ['layers', i] 根层\n * ['layers', i, 'rows', j] 表行\n * ['layers', i, 'rows', j, 'cells', k] 单元格\n * ['layers', i, 'rows', j, 'cells', k, 'content'] 格内容层\n * ['layers', i, 'template', 'cells', k] 行模板格(无下标的 template 段)\n * ['layers', i, 'template'] 行模板替身(表级,spec §2.2)\n *\n * 于是「对选中层改字段」= path 拼上字段段(如 + ['position', 'x'])直接得到\n * patch path;选择态住 ui 分支(不进历史),文档写入一律经路径导航。\n * 全模块纯函数、无 DOM。\n */\nimport { resolveChildAt, resolveLayerBox, type Canvas, type Layer, type LayerBox, type TextLayoutPolicies } from '@hankchen/canvas'\n\n/** 图层路径:patch path 前缀形态的只读数组(语法由 isLayerPath 校验) */\nexport type LayerPath = readonly (string | number)[]\n\n/** 索引段必须是安全非负整数 */\nfunction isIndexSegment(value: unknown): value is number {\n return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0\n}\n\n/** 结构校验(不触文档):只回答「像不像一条图层路径」;越界由 resolveLayer 判定 */\nexport function isLayerPath(value: unknown): value is LayerPath {\n if (!Array.isArray(value) || value.length < 2) return false\n if (value[0] !== 'layers' || !isIndexSegment(value[1])) return false\n\n // 下降序列固定:rows → cells → content(可省前段,不可乱序/重复)。行模板\n // 子树以无下标的 'template' 段替代 rows 段(spec §2.2):template 可后接\n // cells 下钻(预览行选中),也可作路径收尾——收尾形态是**行模板替身路径**\n // (大纲模板子树选中/容器解析用),不是普通选择身份。\n const descent = ['rows', 'cells', 'content']\n let stage = 0\n let i = 2\n while (i < value.length) {\n const key = value[i]\n if (stage === 0 && key === 'template') {\n if (i === value.length - 1) return true\n if (value[i + 1] !== 'cells') return false\n stage = 1\n i += 1\n continue\n }\n const expected = descent[stage]\n if (key !== expected) return false\n if (expected === 'content') return i === value.length - 1\n if (!isIndexSegment(value[i + 1])) return false\n stage += 1\n i += 2\n }\n return true\n}\n\n/** 路径是否落在行模板子树内(含 'template' 段;选中/属性写入的模板态分流用) */\nexport function isTemplateSubtreePath(path: LayerPath): boolean {\n return path.includes('template')\n}\n\n/**\n * 根层路径判定(工单 14):置顶/置底等 priority 语义只作用于根层(容器内行/格\n * 是数组序语义,priority 不参与排序)——右键菜单与快捷键入口据此裁剪动作可用态。\n */\nexport function isRootLayerPath(path: LayerPath): boolean {\n return isLayerPath(path) && path.length === 2\n}\n\n/** 路径值相等(选择态变更短路依赖它;引用比较对每次新造的数组无效) */\nexport function pathsEqual(a: LayerPath | null, b: LayerPath | null): boolean {\n if (a === b) return true\n if (!a || !b || a.length !== b.length) return false\n for (let i = 0; i < a.length; i += 1) {\n if (a[i] !== b[i]) return false\n }\n return true\n}\n\n/**\n * 前缀判定:path 落在 prefix 子树内(含相等)。跨容器移动后重挂选择路径用\n * (工单 12)——子树整体换容器时,移动前捕获的路径按前缀重挂到新位置。\n */\nexport function pathStartsWith(path: LayerPath, prefix: LayerPath): boolean {\n if (path.length < prefix.length) return false\n for (let i = 0; i < prefix.length; i += 1) {\n if (path[i] !== prefix[i]) return false\n }\n return true\n}\n\n/**\n * 路径是否处于锁定子树(canvas-web-layer-lock 工单 01):lockedPaths 中存在其\n * 前缀路径即锁定(锁定恒为根层路径,即根祖先被锁)。谓词收口一处——hitTest\n * 过滤、动作 guard(beginDrag/deleteLayer)、gizmo、面板投影共用,防各处手写\n * 路径比对;语义与 spec §2 同源:锁根层即整子树受保护。\n */\nexport function isLockedPath(path: LayerPath, lockedPaths: readonly LayerPath[]): boolean {\n return lockedPaths.some((locked) => pathStartsWith(path, locked))\n}\n\n/** 沿路径导航(根可为 immer draft:同一套下降逻辑同时服务只读解析与事务写入) */\nfunction navigate(root: unknown, path: LayerPath): unknown {\n // (属性名, 索引) 成对推进;'template' 与尾段 'content' 无索引,落到节点本身\n let current: unknown = root\n let i = 0\n while (i < path.length) {\n const container = current as Record<string, unknown> | null\n if (container === null || typeof container !== 'object') return null\n const key = path[i]\n if (typeof key !== 'string') return null\n current = container[key]\n if (current === null || current === undefined) return null\n if (key === 'template') {\n i += 1\n continue\n }\n if (i + 1 >= path.length) break\n const list = current as readonly unknown[]\n const index = path[i + 1]\n if (!Array.isArray(list) || typeof index !== 'number') return null\n current = list[index]\n if (current === null || current === undefined) return null\n i += 2\n }\n return current\n}\n\n/** 对文档解析路径:越界/形态与文档不符返回 null(doc 侧校验) */\nexport function resolveLayer(doc: Canvas, path: LayerPath): Layer | null {\n if (!isLayerPath(path)) return null\n const layer = navigate(doc, path)\n if (layer === null || typeof layer !== 'object' || !('type' in layer)) return null\n return layer as Layer\n}\n\n/**\n * 路径所属根层(layer-panel-ux 工单 10):LayerBase 面字段(visible 等)只作用\n * 根层,任意深度的路径第 2 段即根层下标——gizmo 隐藏过滤与右键菜单显隐标签等\n * 绑定层读取位共用,收口「path[1] 是根层」的路径结构知识。越界返回 null。\n */\nexport function rootLayerOf(doc: Canvas, path: LayerPath): Layer | null {\n const index = path[1]\n if (typeof index !== 'number' || !Number.isSafeInteger(index) || index < 0) return null\n return doc.layers[index] ?? null\n}\n\n/**\n * 级联归属链(结构纯函数,无需文档):格内容 → 格 → 行 → 表;根层无父级。\n * Escape 升级沿此链逐级取父,链尽即清空选择。模板子树链(spec §2.2):模板格\n * 内容 → 模板格 → 行模板(替身路径,可选中)→ 表——行模板是链上正式一级。\n */\nexport function selectionParentPath(path: LayerPath): LayerPath | null {\n if (path.length < 2) return null\n if (path[path.length - 1] === 'content') return path.slice(0, -1)\n // 行模板替身收尾:template 段无下标容器对,父级 = 表\n if (path[path.length - 1] === 'template') return path.slice(0, -1)\n if (path.length > 2) {\n // 模板格的父级 = 行模板替身路径(保留 template 段,不再跳过)\n return path.slice(0, -2)\n }\n return null\n}\n\n/**\n * 路径处图层的绝对盒(下钻几何经 canvas 的 resolveChildAt,与渲染模板\n * walkLayer 同一套推进公式:行纵向累加、格横向累加、格内容与格同原点)。\n * gizmo 选择框、命中测试、适应选区共用,保证与绘制不漂移。\n */\nexport function layerBoxByPath(\n doc: Canvas,\n path: LayerPath,\n policies?: TextLayoutPolicies,\n): LayerBox | null {\n if (!isLayerPath(path)) return null\n\n const rootIndex = path[1] as number\n const root = doc.layers[rootIndex]\n if (root === undefined) return null\n let layer: Layer = root\n let box = resolveLayerBox(root, 0, 0, doc.width, doc.height, policies)\n\n // 逐段下钻:每段经共享几何解析目标孩子(越界/形态不符返回 null);\n // 'template' 段无下标,盒与首行同位(resolveChildAt 同一推进公式)\n let i = 2\n while (i < path.length) {\n const key = path[i] as 'rows' | 'cells' | 'content' | 'template'\n if (key === 'template') {\n const child = resolveChildAt(layer, box, 'template', 0, policies)\n if (!child) return null\n layer = child.layer\n box = child.box\n i += 1\n continue\n }\n const index = path[i + 1] as number\n const child = resolveChildAt(layer, box, key, index, policies)\n if (!child) return null\n layer = child.layer\n box = child.box\n i += 2\n }\n return box\n}\n\n/**\n * 结构变更后的路径重映射(纯函数,工单 10):containerPath 容器的 key 子列表发生\n * 「摘除 from 处 1 项、插回 to 处」(to = 插入后的最终下标;to === from 即纯删除)。\n * 前缀匹配容器外的路径原样返回;被移出容器的子树返回 null(调用方落地为清除选择)。\n */\nexport function remapPathAfterSplice(\n path: LayerPath,\n containerPath: LayerPath,\n key: 'layers' | 'rows' | 'cells',\n from: number,\n to: number,\n): LayerPath | null {\n if (path.length < containerPath.length + 2) return path\n for (let i = 0; i < containerPath.length; i += 1) {\n if (path[i] !== containerPath[i]) return path\n }\n if (path[containerPath.length] !== key) return path\n const index = path[containerPath.length + 1]\n if (typeof index !== 'number') return path\n const rest = path.slice(containerPath.length + 2)\n const head: LayerPath = [...containerPath, key]\n\n if (from === to) {\n if (index === from) return null\n return index > from ? [...head, index - 1, ...rest] : path\n }\n if (index === from) return [...head, to, ...rest]\n if (from < index && index <= to) return [...head, index - 1, ...rest]\n if (to <= index && index < from) return [...head, index + 1, ...rest]\n return path\n}\n","/**\n * 吸附数学 + 参考线轴几何(ruler-guides-snap 工单 01,ADR 0012;工单 07 起缩放\n * 手势共用同一条求位缝——拖动九点与缩放移动缘是 resolveSnapPoints 的两个特例)。\n *\n * 吸附(snap)语义(术语见根 CONTEXT.md):拖动图层时其盒缘与中心(九点)自动\n * 对齐到吸附源的编辑器行为——源为其他可见根层盒的缘与中心、画布水平/垂直中轴、\n * 参考线轴;画布四边不设轴(精确贴边归第一批对齐画布按钮,ADR 0011)。命中阈值\n * 按屏幕像素常量计(缩放无关,换算场景值后使用),多轴命中取最近,一次拖动至多\n * 吸一垂直轴 + 一水平轴。吸附是拖动过程的连续行为,与对齐画布的一次性动作分立。\n *\n * 参考线(guide)是使用者从标尺拖出的会话级对位轴:住内核 ui 分支——不进历史、\n * 不写 graph、保存零改动;本模块只定义其形态与「供轴」,增删动作在会话门面。\n * 全模块纯函数、无 DOM。\n */\nimport { resolveLayerBox, type Canvas, type LayerBox, type TextLayoutPolicies } from '@hankchen/canvas'\n\n/** 轴向:vertical = 竖直线(修正 x)、horizontal = 水平线(修正 y),场景坐标 */\nexport type GuideOrientation = 'vertical' | 'horizontal'\n\n/** 参考线:会话级对位轴(id 会话内自增,position 为场景坐标) */\nexport interface Guide {\n readonly id: number\n readonly orientation: GuideOrientation\n readonly position: number\n}\n\n/** 吸附轴来源:其他可见根层盒 / 画布中轴 / 参考线 */\nexport type SnapAxisSource = 'layer' | 'canvas-center' | 'guide'\n\n/** 吸附轴:orientation 为线的方向,position 为场景坐标(源与命中同形) */\nexport interface SnapAxis {\n readonly orientation: GuideOrientation\n readonly position: number\n readonly source: SnapAxisSource\n}\n\n/** 吸附求位的盒几何入参(LayerBox 的位置尺寸切片) */\nexport type SnapBoxGeometry = Pick<LayerBox, 'x' | 'y' | 'width' | 'height'>\n\n/** 吸附求位结果:两轴修正量 + 命中轴(至多一垂直 + 一水平,吸附线呈现消费) */\nexport interface SnapResolution {\n readonly dx: number\n readonly dy: number\n readonly axes: readonly SnapAxis[]\n}\n\n/** 拖动吸附屏幕阈值(css 像素,缩放无关;场景阈值 = 阈值 / zoom) */\nexport const SNAP_THRESHOLD_SCREEN_PX = 6\n\n/** 屏幕阈值 → 场景阈值(放大时场景窗口收紧,屏幕手感恒定) */\nexport function snapThresholdScene(zoom: number): number {\n return SNAP_THRESHOLD_SCREEN_PX / zoom\n}\n\n/**\n * 吸附层盒来源:可见根层盒(渲染端整层跳过、命中测试整子树退出——同一 visible\n * 过滤,hitTest 同款),并排除拖动层自身根(自缘不供轴,防自身 delta 0 命中噪声)。\n * excludeRootIndex 传 null = 排除面为空(copyMode 首移:副本未入库、源层供轴,\n * alt-drag-paste 工单 01)。\n */\nexport function visibleRootBoxes(\n doc: Canvas,\n excludeRootIndex: number | null,\n policies?: TextLayoutPolicies,\n): LayerBox[] {\n const boxes: LayerBox[] = []\n for (let i = 0; i < doc.layers.length; i += 1) {\n if (excludeRootIndex !== null && i === excludeRootIndex) continue\n const layer = doc.layers[i]!\n if (layer.visible === false) continue\n boxes.push(resolveLayerBox(layer, 0, 0, doc.width, doc.height, policies))\n }\n return boxes\n}\n\n/**\n * 吸附轴集合:根层盒左/中/右 + 上/中/下缘(source=layer)+ 画布水平/垂直中轴\n * (source=canvas-center,四边不设轴)+ 参考线轴(source=guide)。集合次序即\n * 多轴平局时的取先次序:层盒 → 画布中轴 → 参考线。\n */\nexport function snapAxesFromBoxes(\n canvasWidth: number,\n canvasHeight: number,\n boxes: readonly LayerBox[],\n guides: readonly Guide[],\n): SnapAxis[] {\n const axes: SnapAxis[] = []\n for (const box of boxes) {\n axes.push(\n { orientation: 'vertical', position: box.x, source: 'layer' },\n { orientation: 'vertical', position: box.x + box.width / 2, source: 'layer' },\n { orientation: 'vertical', position: box.x + box.width, source: 'layer' },\n { orientation: 'horizontal', position: box.y, source: 'layer' },\n { orientation: 'horizontal', position: box.y + box.height / 2, source: 'layer' },\n { orientation: 'horizontal', position: box.y + box.height, source: 'layer' },\n )\n }\n axes.push(\n { orientation: 'vertical', position: canvasWidth / 2, source: 'canvas-center' },\n { orientation: 'horizontal', position: canvasHeight / 2, source: 'canvas-center' },\n )\n for (const guide of guides) {\n axes.push({ orientation: guide.orientation, position: guide.position, source: 'guide' })\n }\n return axes\n}\n\n/**\n * 单轴求位:拖动盒在该轴向上的三点(左/中/右或上/中/下)对全部同向轴取\n * |delta| 最小且 ≤ 阈值的命中;无命中返回 null。平局取先遇到的轴(集合次序)。\n */\nfunction nearestAxisHit(\n points: readonly number[],\n axes: readonly SnapAxis[],\n orientation: GuideOrientation,\n threshold: number,\n): { delta: number; axis: SnapAxis } | null {\n let best: { delta: number; axis: SnapAxis } | null = null\n for (const axis of axes) {\n if (axis.orientation !== orientation) continue\n for (const point of points) {\n const delta = axis.position - point\n const distance = Math.abs(delta)\n if (distance > threshold) continue\n if (best === null || distance < Math.abs(best.delta)) best = { delta, axis }\n }\n }\n return best\n}\n\n/**\n * 按轴向分组的求位点集(resolveSnapPoints 入参):拖动九点与缩放移动缘共用\n * (工单 07——缩放只给移动缘位点,见 spatial/resize.resizeSnapPoints)。\n */\nexport interface SnapPoints {\n readonly vertical: readonly number[]\n readonly horizontal: readonly number[]\n}\n\n/**\n * 任意位点集吸附求位:垂直位点对垂直轴、水平位点对水平轴各取最近命中(已对齐\n * delta=0 也回显命中轴——吸附线呈现「已对齐」状态);每轴至多一条命中轴,两轴\n * 修正量由调用方并入本次几何写入(同一事务、同一历史步)。拖动盒九点吸附\n * (resolveSnap)是本函数位点集的特例,缩放移动缘吸附(工单 07)是另一特例。\n */\nexport function resolveSnapPoints(\n points: SnapPoints,\n axes: readonly SnapAxis[],\n threshold: number,\n): SnapResolution {\n const vertical = nearestAxisHit(points.vertical, axes, 'vertical', threshold)\n const horizontal = nearestAxisHit(points.horizontal, axes, 'horizontal', threshold)\n return {\n dx: vertical ? vertical.delta : 0,\n dy: horizontal ? horizontal.delta : 0,\n axes: [\n ...(vertical ? [vertical.axis] : []),\n ...(horizontal ? [horizontal.axis] : []),\n ],\n }\n}\n\n/**\n * 拖动盒九点吸附求位:盒左/中/右三点对垂直轴、上/中/下三点对水平轴各取最近\n * 命中(已对齐 delta=0 也回显命中轴——吸附线呈现「已对齐」状态);返回的修正量\n * 由调用方并入本次拖动的 position 写入(同一事务、同一历史步)。\n */\nexport function resolveSnap(\n box: SnapBoxGeometry,\n axes: readonly SnapAxis[],\n threshold: number,\n): SnapResolution {\n return resolveSnapPoints(\n {\n vertical: [box.x, box.x + box.width / 2, box.x + box.width],\n horizontal: [box.y, box.y + box.height / 2, box.y + box.height],\n },\n axes,\n threshold,\n )\n}\n","/**\n * 画拉建层橡皮筋求位几何(drag-create 工单 01,术语见根 CONTEXT.md「画拉建层」\n * 「武装」词条)。\n *\n * 拖拽期两点正规化出橡皮筋矩形(x = min、width = |dx|,y 同,反向拖不翻转);\n * QrCodeLayer 钳 height := width(高随宽从动保持方形,八柄缩放「QR 从动轴不\n * 采纳」同门);橡皮筋左右/上下缘各两点进 resolveSnapPoints——吸附数学零新增,\n * 命中修正整矩形平移并入几何(两点正规化与 QR 方形在修正后原样保持)。死区常量\n * 屏幕像素口径同 SNAP_THRESHOLD_SCREEN_PX(场景阈值 = 阈值 / zoom,缩放无关)。\n * 全模块纯函数、无 DOM;会话编排(武装/落库/解除)在会话门面。\n */\nimport type { LayerType } from '@hankchen/canvas'\n\nimport type { Point } from './camera'\nimport { resolveSnapPoints, type SnapAxis, type SnapBoxGeometry } from './snap'\n\n/** 画拉死区阈值(css 像素,缩放无关;场景阈值 = 阈值 / zoom):位移不越过即视作\n * 点击,endCreate 回落「缺省尺寸、左上角对准点击点」,不产生零尺寸层 */\nexport const CREATE_DEAD_ZONE_SCREEN_PX = 4\n\n/** 橡皮筋求位结果:并入吸附修正后的矩形 + 命中轴(至多一垂直 + 一水平) */\nexport interface CreateRectResult {\n readonly rect: SnapBoxGeometry\n readonly axes: readonly SnapAxis[]\n}\n\n/**\n * 画拉橡皮筋求位:两点正规化(起点场景坐标 + 当前点 → 左上角原点矩形)→\n * QR 钳 height := width → 左右/上下缘各两点对轴集合求位(已有已对齐 delta=0\n * 也回显命中轴的语义,dragTo/resizeTo 同门)→ 修正整矩形平移并入。钳方先于\n * 吸附——QR 的方边(y + width)才是真实的下缘吸附位点。\n */\nexport function createRubberBandRect(\n type: LayerType,\n start: Point,\n current: Point,\n axes: readonly SnapAxis[],\n threshold: number,\n): CreateRectResult {\n const x = Math.min(start.x, current.x)\n const y = Math.min(start.y, current.y)\n const width = Math.abs(current.x - start.x)\n const height = type === 'QrCodeLayer' ? width : Math.abs(current.y - start.y)\n const resolution = resolveSnapPoints(\n { vertical: [x, x + width], horizontal: [y, y + height] },\n axes,\n threshold,\n )\n return {\n rect: { x: x + resolution.dx, y: y + resolution.dy, width, height },\n axes: resolution.axes,\n }\n}\n","/**\n * 八柄缩放几何(工单 07)。\n *\n * 缩放语义(工单验收项 1/2,术语见根 CONTEXT.md「八柄缩放」词条):编辑者拖动\n * 选中框上的八柄(四角 + 四边中点)调整图层盒——对缘固定、移动缘随指针;\n * minSize 钳位保证不产生负宽高、不翻转(盒模型契约无通用 angle,明确不做\n * 旋转/翻转)。全部纯函数、无 DOM,缩放手势的文档写回在会话门面(editor.ts\n * resizeTo:position 反解 + auto 标志采纳 + 表格强同步)。\n *\n * 角色可缩放面与属性面板的字段权威过滤(editor-vue fieldSchema ROLE_HIDDEN_KEYS\n * 语义同构):解码/fromGraph 强同步的尺寸字段不可经柄改——行宽=表宽、格内容\n * 宽高=格尺寸;柄集合 = 轴可缩放面的柄投影,gizmo 渲染与手势起点共用同一条缝。\n */\nimport { type Canvas, type LayerBox } from '@hankchen/canvas'\n\nimport { resolveLayer, type LayerPath } from '../shared/layerPath'\nimport type { SnapPoints } from './snap'\n\n/** 八柄:四角 + 四边中点(命名 = 柄在盒上的方位) */\nexport const RESIZE_HANDLES = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'] as const\n\nexport type ResizeHandle = (typeof RESIZE_HANDLES)[number]\n\n/** 缩放最小边长(场景 px):钳位下限,杜绝负宽高与零尺寸不可见层 */\nexport const RESIZE_MIN_SIZE_PX = 1\n\n/** 缩放几何的盒入参(LayerBox 的位置尺寸切片,与吸附求位入参同形) */\nexport type ResizeBoxGeometry = Pick<LayerBox, 'x' | 'y' | 'width' | 'height'>\n\n/** 缩放求位的盒结果(绝对坐标,写回面消费) */\nexport interface ResizeBoxResult {\n readonly x: number\n readonly y: number\n readonly width: number\n readonly height: number\n}\n\n/** 柄的方向分类(单点维护:resizeBox/resizeSnapPoints/轴判定共读同一份) */\nconst WEST_HANDLES: ReadonlySet<ResizeHandle> = new Set(['nw', 'w', 'sw'])\nconst EAST_HANDLES: ReadonlySet<ResizeHandle> = new Set(['ne', 'e', 'se'])\nconst NORTH_HANDLES: ReadonlySet<ResizeHandle> = new Set(['nw', 'n', 'ne'])\nconst SOUTH_HANDLES: ReadonlySet<ResizeHandle> = new Set(['sw', 's', 'se'])\n\n/** 柄是否作用于横向(宽轴):东/西缘与四角 */\nexport function handleResizesWidth(handle: ResizeHandle): boolean {\n return WEST_HANDLES.has(handle) || EAST_HANDLES.has(handle)\n}\n\n/** 柄是否作用于纵向(高轴):南/北缘与四角 */\nexport function handleResizesHeight(handle: ResizeHandle): boolean {\n return NORTH_HANDLES.has(handle) || SOUTH_HANDLES.has(handle)\n}\n\n/**\n * 八柄缩放求位:对缘固定、移动缘 = 起始缘 + 位移,minSize 钳位(先钳尺寸再\n * 反推移动缘坐标——拖过对缘时停在 minSize 而不翻转,对缘坐标严格不动)。\n * 非本柄轴的位移分量忽略(n 不吃 dx、e 不吃 dy)。\n */\nexport function resizeBox(\n start: ResizeBoxGeometry,\n handle: ResizeHandle,\n dx: number,\n dy: number,\n minSize: number = RESIZE_MIN_SIZE_PX,\n): ResizeBoxResult {\n const west = WEST_HANDLES.has(handle)\n const north = NORTH_HANDLES.has(handle)\n const width = west\n ? start.width - dx\n : EAST_HANDLES.has(handle)\n ? start.width + dx\n : start.width\n const height = north\n ? start.height - dy\n : SOUTH_HANDLES.has(handle)\n ? start.height + dy\n : start.height\n const clampedWidth = Math.max(width, minSize)\n const clampedHeight = Math.max(height, minSize)\n return {\n x: west ? start.x + start.width - clampedWidth : start.x,\n y: north ? start.y + start.height - clampedHeight : start.y,\n width: clampedWidth,\n height: clampedHeight,\n }\n}\n\n/**\n * 本步位移下的移动缘场景坐标(钳位前——吸附修正施加在指针位移上,再进\n * resizeBox 钳位,吸附不破「对缘固定 + minSize」语义)。只收真正被写动的缘:\n * 角柄双缘、边柄单缘;固定缘与盒中心不参与缩放吸附(中心对齐是拖动期行为,\n * 缩放期跟随动缘即可)。\n */\nexport function resizeSnapPoints(\n start: ResizeBoxGeometry,\n handle: ResizeHandle,\n dx: number,\n dy: number,\n): SnapPoints {\n const vertical: number[] = []\n const horizontal: number[] = []\n if (WEST_HANDLES.has(handle)) vertical.push(start.x + dx)\n if (EAST_HANDLES.has(handle)) vertical.push(start.x + start.width + dx)\n if (NORTH_HANDLES.has(handle)) horizontal.push(start.y + dy)\n if (SOUTH_HANDLES.has(handle)) horizontal.push(start.y + start.height + dy)\n return { vertical, horizontal }\n}\n\n/** 可缩放轴(width = 横向宽字段可写、height = 纵向高字段可写) */\nexport interface ResizeAxes {\n readonly width: boolean\n readonly height: boolean\n}\n\n/** 双轴皆不可缩放(格内容/行模板替身/路径悬空的共同回退) */\nconst NO_AXES: ResizeAxes = { width: false, height: false }\n\n/**\n * 路径处图层的可缩放轴(角色权威过滤,与 editor-vue fieldSchema\n * ROLE_HIDDEN_KEYS 逐角色同构——面板隐藏的尺寸字段即柄不可写的轴):\n * - root:双轴(根表宽写经 canonicalize 联动行宽,面板同门放行);\n * - row:仅纵向(行宽 = 表宽强同步,面板同门隐藏 width);\n * - cell:双轴(内容宽/行高随 canonicalize 强同步跟随,面板同门放行);\n * - content / templateContent:content 双向强同步全隐藏;模板格内容宽度耦合\n * 沿用、高度豁免放行(ADR 0006 同门)→ 仅纵向。模板内容路径形如\n * ['layers', i, 'template', 'cells', c, 'content']——'template' 段在\n * len−4(len−1 content、len−2 格下标、len−3 'cells'),按尾段回数定位;\n * - 行模板替身(路径尾 'template'):声明体尺寸不经画布柄改(转换入口/面板\n * 语义面),双轴皆禁。\n */\nexport function resizableAxesAt(doc: Canvas, path: LayerPath): ResizeAxes {\n const layer = resolveLayer(doc, path)\n if (!layer) return NO_AXES\n if (path[path.length - 1] === 'content') {\n const templateContent = path.length > 4 && path[path.length - 4] === 'template'\n return templateContent ? { width: false, height: true } : NO_AXES\n }\n if (path[path.length - 1] === 'template') return NO_AXES\n const containerKey = path[path.length - 2]\n if (containerKey === 'rows') return { width: false, height: true }\n if (containerKey === 'cells') return { width: true, height: true }\n return { width: true, height: true }\n}\n\n/**\n * 路径处图层的可用柄集合:柄的每一条作用轴都在可缩放面内才可用——角柄要求\n * 双轴皆可(行上角柄退化成边柄会造成「拖角只动一边」的歧义,不出),gizmo\n * 渲染与手势起点共用(beginResize 同缝拒绝不可用柄)。\n */\nexport function resizeHandlesAt(doc: Canvas, path: LayerPath): readonly ResizeHandle[] {\n const axes = resizableAxesAt(doc, path)\n return RESIZE_HANDLES.filter((handle) => {\n const needsWidth = handleResizesWidth(handle)\n const needsHeight = handleResizesHeight(handle)\n return (!needsWidth || axes.width) && (!needsHeight || axes.height)\n })\n}\n\n/** 手柄命中半径(css 像素,缩放无关,场景半径 = 半径 / zoom;视觉柄 8px 见 gizmo) */\nexport const RESIZE_HANDLE_HIT_PX = 6\n\n/** 柄在盒上的中心点(场景坐标:四角 + 四边中点;gizmo 柄绘制同源消费) */\nexport function resizeHandlePoint(\n box: ResizeBoxGeometry,\n handle: ResizeHandle,\n): { x: number; y: number } {\n const right = box.x + box.width\n const bottom = box.y + box.height\n switch (handle) {\n case 'nw':\n return { x: box.x, y: box.y }\n case 'n':\n return { x: box.x + box.width / 2, y: box.y }\n case 'ne':\n return { x: right, y: box.y }\n case 'e':\n return { x: right, y: box.y + box.height / 2 }\n case 'se':\n return { x: right, y: bottom }\n case 's':\n return { x: box.x + box.width / 2, y: bottom }\n case 'sw':\n return { x: box.x, y: bottom }\n case 'w':\n return { x: box.x, y: box.y + box.height / 2 }\n }\n}\n\n/**\n * 场景点命中可用柄:距柄中心 ≤ 屏幕半径折算的场景半径即命中;多柄同时命中\n * (极小盒边中点与角点重合)取 RESIZE_HANDLES 次序的先者。只测传入的可用柄\n * 集合(内核 resizeHandlesAt 的投影),不在集合内的柄不命中。\n */\nexport function resizeHandleAt(\n handles: readonly ResizeHandle[],\n box: ResizeBoxGeometry,\n sceneX: number,\n sceneY: number,\n zoom: number,\n): ResizeHandle | null {\n const tolerance = RESIZE_HANDLE_HIT_PX / zoom\n for (const handle of handles) {\n const point = resizeHandlePoint(box, handle)\n if (Math.hypot(sceneX - point.x, sceneY - point.y) <= tolerance) return handle\n }\n return null\n}\n","/**\n * 数据源 schema 方言编译器(content-completion 工单 02/08,spec §2 + §6 D6–D8)。\n *\n * D6 钉定:注入边界一次性转译为内部形状树(不在查询期惰性解析原始 schema,\n * 行业惯例见 schema-completion-research.md)。方言子集 = JSON Schema draft-07 的\n * 结构关键词(`properties` / `items` / `type`)+ 注解关键词(`description` /\n * `title` / `additionalProperties: true`)+ 引用关键词(`$ref` 内部 JSON Pointer、\n * `definitions`/`$defs` 命名池);其余关键词(`required`/`format`/`enum`/`const`/\n * `allOf` 族/`patternProperties`/`$schema`/`$id` 及一切未识别键)宽松忽略,节点按\n * 已识别字段归一(可能变叶子)。\n *\n * 声明契约(D1 钉定):schema 声明即 `compile(canvas, dataset)` 收到的 data\n * 载荷形状——根上下文候选 = 载荷顶层键;根必须是带 properties 的对象声明。\n *\n * 降级分级(D7):\n * - **整份拒绝**仅保留根级结构性问题(在原始文档上判定,拒绝面与 walker 时代\n * 全等):根非对象 / 根缺 properties 层级(含根级 `$ref`——根不解引用)/\n * `reserved_root_key`(根级键 `row` 或 `$` 前缀,前移填充期硬错误,对应\n * `CanvasHydrator::assertReservedRootKeys`;仅根级保留,嵌套层同名键合法)。\n * - **局部故障**(`$ref` 断链 / 外部指针 / 环引用(活跃路径 seen-set 判定)/\n * 目标形态不符)→ 该节点降级为叶子(保留本地 type/description/title 注解)+\n * diagnostics 逐条记录,树其余部分照常服务;注入期由\n * normalizeExpressionSchemaSource 汇总 console.warn 一次(D12 单点收口,\n * 诊断明细可经 parseExpressionSchema 直取,工单 11 面板展示)。\n * - 深层识别关键词**形态不符**(如 type: 3 / property 子声明非对象)视同未识别\n * 键宽松忽略:不诊断不降级,节点按剩余字段归一(可能变叶子)。\n *\n * $ref 语义(D7):任意内部 `#/` JSON Pointer(先整段百分号解码——`%25`→`%` 等,\n * 解码失败记 broken_ref——再按 `/` 切段、逐段 `~1`→`/`、`~0`→`~` 反转义),\n * definitions/$defs 命名池只作指针目标不进候选树;外部/远程指针不支持,归宿主\n * 预内联。目标为基底,本地 `description` 覆盖目标同名注解(对 draft-07 兄弟语义\n * 的唯一偏离——兄弟 description 是现成候选说明,严格性无收益;其余兄弟关键词按\n * draft-07 忽略)。环引用防环用活跃路径 seen-set(对齐 VS Code\n * resolveSchemaContent 范式):seen 只含当前 DFS 祖先链,菱形引用不误判;\n * 该守卫兼防宿主程序化构造的对象别名自嵌套(JSON 文档天然无环)。\n *\n * D3 钉定(2026-09-30 由 D9 修订):数组类型节点 items 键树下钻保留在**下钻层**\n * (schemaNodeAtPath / resolveRowSchema,rowsPath 行 schema 解析与 `row.*`/`$index`\n * 机制零影响);**枚举层**(schemaChildEntries 候选视图)已分叉——数组节点\n * (type 声明为 array 或声明了 items)收敛为叶子、不沿 items 链穿透:具名段下钻\n * 数组在求值器(InterpolationEvaluator::navigateValue)是静默空串(缺字段信号),\n * 补全不得怂恿写渲染为空白的表达式;类型徽标仍显示 array 本身,不特供「供表格\n * rowsPath 使用」文案(为表达式内置函数留前瞻口,spec D9)。open 节点\n * (additionalProperties: true)即随枚举结果携带 open 信号(D10,供工单 10 浮层\n * 占位提示;信号面与候选空否无关)。\n *\n * 全模块零 DOM、零内部依赖(editor-shared-isolation);除\n * normalizeExpressionSchemaSource 的 console 警告收口(注入期汇总一次)外均为纯函数。\n */\n\n/** schema 方言形状树节点(parse 归一化产物,properties 冻结为 Map 防宿主变异) */\nexport interface ExpressionSchemaNode {\n /** 主类型徽标数据:type 关键词归一(数组取首个非 null;未声明为 undefined) */\n type?: string\n /** 展示回落注解(D8):浮层取 description ?? title */\n title?: string\n description?: string\n /** 开放映射标记(D10):`additionalProperties: true`;动态字段实际键由宿主合入 properties */\n open?: boolean\n properties?: ReadonlyMap<string, ExpressionSchemaNode>\n items?: ExpressionSchemaNode\n}\n\n/** schema 子集一条目(枚举子候选用:键名 + 节点元信息) */\nexport interface ExpressionSchemaChildEntry {\n key: string\n node: ExpressionSchemaNode\n}\n\n/** 声明期拒绝原因:reserved_root_key = 前移的填充期硬错误;invalid_schema = 形态非法 */\nexport type ExpressionSchemaRejectReason = 'reserved_root_key' | 'invalid_schema'\n\n/** 局部故障诊断码(D7 四类,全部 $ref 解析面;节点降叶子 + 树其余照常服务) */\nexport type ExpressionSchemaDiagnosticCode = 'broken_ref' | 'external_ref' | 'circular_ref' | 'target_shape_mismatch'\n\n/** 一条局部故障诊断:path = 降级节点在原始 schema 文档中的声明路径(JSON Pointer 风格) */\nexport interface ExpressionSchemaDiagnostic {\n path: string\n code: ExpressionSchemaDiagnosticCode\n detail: string\n}\n\nexport type ExpressionSchemaParse =\n | { ok: true; schema: ExpressionSchemaNode; diagnostics: readonly ExpressionSchemaDiagnostic[] }\n | { ok: false; reason: ExpressionSchemaRejectReason; detail: string }\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** type 关键词归一:string 直取;string[] 取首个非 null/空串(全 null/空为 undefined) */\nfunction normalizeSchemaType(value: string | readonly string[]): string | undefined {\n if (typeof value === 'string') return value === '' ? undefined : value\n const primary = value.find((entry) => entry !== 'null' && entry !== '')\n return primary\n}\n\n/** 节点注解子形状(降级叶子 = 仅注解) */\ntype SchemaAnnotations = Pick<ExpressionSchemaNode, 'type' | 'description' | 'title'>\n\n/** 节点注解的宽松提取:形态不符的键视同未识别忽略(工单 08 宽松面) */\nfunction annotationsOf(raw: Record<string, unknown>): SchemaAnnotations {\n const node: SchemaAnnotations = {}\n const rawType = raw['type']\n if (typeof rawType === 'string') {\n const type = normalizeSchemaType(rawType)\n if (type !== undefined) node.type = type\n } else if (Array.isArray(rawType)) {\n // spec §2:string[] 取首个非 null;非 string 元素宽松剔除(形态不符不整体作废)\n const type = normalizeSchemaType(rawType.filter((entry): entry is string => typeof entry === 'string'))\n if (type !== undefined) node.type = type\n }\n const rawDescription = raw['description']\n if (typeof rawDescription === 'string') node.description = rawDescription\n const rawTitle = raw['title']\n if (typeof rawTitle === 'string') node.title = rawTitle\n return node\n}\n\n/** 声明路径段转义(JSON Pointer 键级 ~0/~1),仅供诊断 path 展示 */\nfunction pointerEscape(key: string): string {\n return key.replace(/~/g, '~0').replace(/\\//g, '~1')\n}\n\n/** JSON Pointer 键级反转义(RFC 6901):单趟左到右,~1→/、~0→~;未跟 0/1 的 ~ 按字面保留 */\nfunction unescapePointerToken(token: string): string {\n let out = ''\n for (let i = 0; i < token.length; i += 1) {\n const ch = token[i]\n if (ch === '~' && (token[i + 1] === '0' || token[i + 1] === '1')) {\n out += token[i + 1] === '0' ? '~' : '/'\n i += 1\n } else {\n out += ch\n }\n }\n return out\n}\n\ntype InternalRefResolution = { ok: true; target: unknown } | { ok: false; why: string }\n\n/**\n * 内部 `#/` 指针解析(仅纯对象键导航):先整段百分号解码(URI 片段层,`%25`→`%`;\n * 无效编码=作者错误,显式断链不静默),再切段、逐段反转义。空片段(`#`)= 根文档。\n */\nfunction resolveInternalRef(ref: string, root: Record<string, unknown>): InternalRefResolution {\n let fragment = ref.slice(1)\n try {\n fragment = decodeURIComponent(fragment)\n } catch {\n return { ok: false, why: '指针片段含无效百分号编码' }\n }\n // 剥掉前导 '/'(JSON Pointer 以 / 起段);空片段(#)= 根文档零段\n const tokens = fragment === '' ? [] : fragment.replace(/^\\//, '').split('/')\n let current: unknown = root\n for (const token of tokens) {\n if (!isPlainObject(current)) return { ok: false, why: `指针途经非对象(段 \"${token}\")` }\n const key = unescapePointerToken(token)\n if (!(key in current)) return { ok: false, why: `指针段未命中(\"${token}\")` }\n current = current[key]\n }\n return { ok: true, target: current }\n}\n\ninterface CompileContext {\n /** 原始 schema 文档($ref 指针导航根) */\n root: Record<string, unknown>\n diagnostics: ExpressionSchemaDiagnostic[]\n /** 活跃路径 seen-set:当前 DFS 祖先链上的原始对象(环引用判定,菱形不误判) */\n activePath: Set<object>\n}\n\n/**\n * 局部故障统一落点(D7):记一条诊断 + 该节点降级为叶子——结构丢弃($ref 目标\n * 本该提供),保留本地注解(type/description/title)供候选展示。\n */\nfunction degradeWithDiagnostic(\n ctx: CompileContext,\n raw: Record<string, unknown>,\n path: string,\n code: ExpressionSchemaDiagnosticCode,\n detail: string,\n): ExpressionSchemaNode {\n ctx.diagnostics.push({ path, code, detail })\n return annotationsOf(raw)\n}\n\nfunction compileSchemaNode(raw: Record<string, unknown>, declPath: string, ctx: CompileContext): ExpressionSchemaNode {\n if (ctx.activePath.has(raw)) {\n // $ref 环由引用点先行判定落诊断(path 指向引用者更可定位);此守卫兜住\n // 无 $ref 的对象别名自嵌套(程序化构造才可能,JSON 文档天然无环)\n return degradeWithDiagnostic(ctx, raw, declPath, 'circular_ref', '节点在活跃路径上重入(环引用),降级为叶子')\n }\n ctx.activePath.add(raw)\n try {\n const rawRef = raw['$ref']\n if (rawRef !== undefined) return compileRefNode(raw, rawRef, declPath, ctx)\n return compileInlineNode(raw, declPath, ctx)\n } finally {\n ctx.activePath.delete(raw)\n }\n}\n\n/** $ref 节点(D7):目标为基底,本地 description 覆盖;解析失败按故障分类降叶子 */\nfunction compileRefNode(raw: Record<string, unknown>, rawRef: unknown, declPath: string, ctx: CompileContext): ExpressionSchemaNode {\n if (typeof rawRef !== 'string') {\n return degradeWithDiagnostic(ctx, raw, declPath, 'broken_ref', '$ref 必须是 string,节点降级为叶子')\n }\n if (!rawRef.startsWith('#')) {\n return degradeWithDiagnostic(\n ctx,\n raw,\n declPath,\n 'external_ref',\n `$ref 指向外部/远程目标 \"${rawRef}\"(仅支持内部 #/ 指针,归宿主预内联),节点降级为叶子`,\n )\n }\n const resolved = resolveInternalRef(rawRef, ctx.root)\n if (!resolved.ok) {\n return degradeWithDiagnostic(ctx, raw, declPath, 'broken_ref', `$ref 内部指针解析失败(${resolved.why}),节点降级为叶子`)\n }\n if (!isPlainObject(resolved.target)) {\n return degradeWithDiagnostic(ctx, raw, declPath, 'target_shape_mismatch', `$ref 目标不是 schema 对象,节点降级为叶子`)\n }\n if (ctx.activePath.has(resolved.target)) {\n return degradeWithDiagnostic(ctx, raw, declPath, 'circular_ref', `$ref 目标在活跃路径上(环引用)\"${rawRef}\",节点降级为叶子`)\n }\n // 基底编译(诊断 path 沿引用指针走,指向定义本体);每次 deref 独立编译,无共享可变状态\n const base = compileSchemaNode(resolved.target, rawRef, ctx)\n const localDescription = raw['description']\n if (typeof localDescription === 'string') base.description = localDescription\n return base\n}\n\n/** 无 $ref 节点:注解 + open 标记 + 结构关键词(形态不符的键宽松忽略) */\nfunction compileInlineNode(raw: Record<string, unknown>, declPath: string, ctx: CompileContext): ExpressionSchemaNode {\n const node: ExpressionSchemaNode = annotationsOf(raw)\n\n if (raw['additionalProperties'] === true) node.open = true\n\n const rawProperties = raw['properties']\n if (isPlainObject(rawProperties)) {\n const properties = new Map<string, ExpressionSchemaNode>()\n for (const [key, child] of Object.entries(rawProperties)) {\n if (!isPlainObject(child)) continue // 子声明非对象宽松跳过,兄弟键照常\n properties.set(key, compileSchemaNode(child, `${declPath}/properties/${pointerEscape(key)}`, ctx))\n }\n node.properties = properties\n }\n\n const rawItems = raw['items']\n if (isPlainObject(rawItems)) node.items = compileSchemaNode(rawItems, `${declPath}/items`, ctx)\n\n return node\n}\n\n/**\n * 声明期编译(D6 注入边界一次性转译):合法 → 节点树 + diagnostics(局部故障\n * 逐条,可为空);根级保留键 → reserved_root_key;根级结构非法 → invalid_schema。\n * 根级判定在原始文档上进行(根级 `$ref` 不解引用,拒绝面与 walker 时代全等)。\n */\nexport function parseExpressionSchema(raw: unknown): ExpressionSchemaParse {\n if (!isPlainObject(raw)) {\n return { ok: false, reason: 'invalid_schema', detail: 'schema 根必须是对象声明' }\n }\n\n // 保留键检查先于形态细化:键名问题无需信任深层结构(对应填充期 reserved_root_key 硬错误)\n const rawProperties = raw['properties']\n if (isPlainObject(rawProperties)) {\n for (const key of Object.keys(rawProperties)) {\n if (key === 'row' || key.startsWith('$')) {\n return { ok: false, reason: 'reserved_root_key', detail: `根级键 \"${key}\" 保留(row. 前缀/$ 名与求值上下文冲突)` }\n }\n }\n }\n if (!isPlainObject(rawProperties)) {\n return { ok: false, reason: 'invalid_schema', detail: '根声明缺 properties 层级(schema 即载荷形状,必须声明顶层键)' }\n }\n\n const diagnostics: ExpressionSchemaDiagnostic[] = []\n const schema = compileSchemaNode(raw, '#', { root: raw, diagnostics, activePath: new Set() })\n return { ok: true, schema, diagnostics }\n}\n\n/**\n * 注入缝收口(工单 03 接线用):parse 失败降级为 null + console.warn(含原因,\n * 不抛错不弹错);编译成功而存在局部故障诊断 → 汇总 warn **恰一次**(D7/D12:\n * 内核单点收口,不逐条告警;诊断明细可经 parseExpressionSchema 直取供工单 11\n * 面板展示)。注入期调用一次,枚举期拿 null 直接无候选、不再逐键告警。\n */\nexport function normalizeExpressionSchemaSource(raw: unknown): ExpressionSchemaNode | null {\n const parsed = parseExpressionSchema(raw)\n if (!parsed.ok) {\n console.warn(\n `[canvas-editor] 数据源 schema 声明降级为无候选(${parsed.reason}: ${parsed.detail})——补全是辅助声明不作权威,不弹错`,\n )\n return null\n }\n if (parsed.diagnostics.length > 0) {\n const lines = parsed.diagnostics.map((diagnostic) => `- ${diagnostic.path}:${diagnostic.code}——${diagnostic.detail}`)\n console.warn(\n `[canvas-editor] 数据源 schema 编译诊断 ${parsed.diagnostics.length} 条(局部降级为叶子,树其余部分照常服务):\\n${lines.join('\\n')}`,\n )\n }\n return parsed.schema\n}\n\n/**\n * 节点的对象形态视图(下钻层专用机制,D3;枚举层候选视图已按 D9 分叉——见\n * schemaChildEntries):properties 声明优先(声明而段未命中 = 死分支,不回退\n * items);无 properties 时沿 items 链找首个带 properties 的节点(数组的数组\n * 穿透,rowsPath 行 schema 解析依赖此链);全链无 = null(叶子分支)。\n */\nfunction objectShapeOf(node: ExpressionSchemaNode): ReadonlyMap<string, ExpressionSchemaNode> | null {\n let cursor: ExpressionSchemaNode | undefined = node\n while (cursor !== undefined) {\n if (cursor.properties !== undefined) return cursor.properties\n cursor = cursor.items\n }\n return null\n}\n\n/**\n * 点路径下钻 walker:逐段经对象形态视图解析(见 objectShapeOf)。全程不抛错:\n * schema 与数据源可能漂移,walker 只管声明树。\n */\nexport function schemaNodeAtPath(node: ExpressionSchemaNode, segments: readonly string[]): ExpressionSchemaNode | null {\n let current = node\n for (const segment of segments) {\n const child = objectShapeOf(current)?.get(segment)\n if (child === undefined) return null\n current = child\n }\n return current\n}\n\n/**\n * 一层子候选枚举结果(D9 候选视图的返回面):候选条目 + open 信号。信号走结果\n * 面扩展,不污染候选条目契约(ExpressionSchemaChildEntry 不加字段)。\n */\nexport interface ExpressionSchemaChildView {\n /** 一层子候选(按声明序);数组节点收敛为叶子 = 空(D9) */\n entries: readonly ExpressionSchemaChildEntry[]\n /** 开放映射信号(D10):节点标 open(additionalProperties: true)时为 true;非 open 恒缺省 */\n open?: boolean\n}\n\n/**\n * 一层子候选枚举(枚举层候选视图,D9 分叉点):数组节点(type 声明为 array 或\n * 声明了 items)**一律**收敛为叶子——具名段下钻数组在求值器是静默空串\n * (navigateValue 缺字段信号),补全不得怂恿写渲染为空白的表达式(spec §2 D9,\n * 2026-09-30 修订 D3);声明自相矛盾(type array 而又带 properties/items)时从疑\n * 同样收敛,对齐 D7 疑点降叶哲学。其余节点取**自身** properties(按声明序),\n * 不沿 items 链穿透。open 信号(D10)随视图透出,供浮层占位提示(工单 10)。\n * 下钻层(schemaNodeAtPath/resolveRowSchema)机制不动,rowsPath/`row.*`/`$index`\n * 零影响(数组的数组穿透不回归)。\n */\nexport function schemaChildEntries(node: ExpressionSchemaNode): ExpressionSchemaChildView {\n if (node.type === 'array' || node.items !== undefined) return { entries: [] }\n const entries =\n node.properties !== undefined ? [...node.properties].map(([key, child]) => ({ key, node: child })) : []\n return node.open === true ? { entries, open: true } : { entries }\n}\n\n/**\n * 行上下文行键树解析(rowsPath 数组 items,CanvasHydrator 行注入 'row' 的形状\n * 本体):rowsPath 走点路径下钻(下钻层 items 链机制,D9 分叉不影响——数组的\n * 数组穿透不回归),命中数组节点取 items;节点无 items(schema 未声明行形状或\n * 漂移)→ null(行子树无候选,结构头 row 仍可用)。\n * 嵌套模板表的行相对 rowsPath 以当前行 schema 为基准解析,由调用方传入对应根。\n */\nexport function resolveRowSchema(root: ExpressionSchemaNode, rowsPath: string): ExpressionSchemaNode | null {\n const segments = rowsPath.split('.')\n if (rowsPath === '' || segments.includes('')) return null\n const node = schemaNodeAtPath(root, segments)\n return node?.items ?? null\n}\n","/**\n * 路径段解析纯函数(content-completion 工单 01,spec §4 纯函数 ②)。\n *\n * 语义权威 = php-canvas-next `InterpolationEvaluator::resolveFragment`(只读对齐,\n * 不改求值器):head 三分类——`row`(行命名空间)/ `$root`(显式取根,与裸名等价)/\n * 裸键(含 `$index` 等 `$` 保留名)——+ 余段原序下钻;空片段、路径含空段\n * (`row..x` / `.x` / `x.`)= `expression_syntax_error` 信号(与求值器同码)。\n *\n * 契约:入参是**已 trim** 的片段表达式(trim 是片段扫描的职责,对齐求值器\n * 先 trim 后 resolve 的次序;本函数不再 trim,空白字符属于键名)。\n * 补全消费侧(schema walker,工单 02)处理「片段内输入 `.` 刷新候选」时,\n * 先剥掉单个尾点(补全点标记)再走本函数——`row.` 按求值器语义是空段错误,\n * 剥点后的 `row` 才是可下钻前缀。\n *\n * 全模块纯函数、无 DOM、零内部依赖(editor-shared-isolation)。\n */\n\n/** 与 PHP 侧 HydrateException 稳定错误码对齐(spec §5.2,三端一致性抓手) */\nexport type ExpressionSyntaxError = 'expression_syntax_error'\n\n/** head 三分类(对齐 resolveFragment 的 match 分支;大小写敏感,Row/$ROOT 是裸键) */\nexport type ExpressionPathHead =\n | { kind: 'row' }\n | { kind: 'root' }\n | { kind: 'bare'; name: string }\n\nexport type ExpressionPathParse =\n | { ok: true; head: ExpressionPathHead; rest: readonly string[] }\n | { ok: false; error: ExpressionSyntaxError }\n\n/** 解析点路径:head 分类 + 余段;语法不合法(空片段/空段)返回错误信号 */\nexport function parseExpressionPath(expr: string): ExpressionPathParse {\n if (expr === '') {\n return { ok: false, error: 'expression_syntax_error' }\n }\n\n const segments = expr.split('.')\n if (segments.includes('')) {\n return { ok: false, error: 'expression_syntax_error' }\n }\n\n const head = segments[0] as string\n const rest = segments.slice(1)\n if (head === 'row') return { ok: true, head: { kind: 'row' }, rest }\n if (head === '$root') return { ok: true, head: { kind: 'root' }, rest }\n return { ok: true, head: { kind: 'bare', name: head }, rest }\n}\n","/**\n * 片段扫描纯函数(content-completion 工单 01,spec §4 纯函数 ①)。\n *\n * 语义逐条对齐 php-canvas-next `InterpolationEvaluator::evaluate` 扫描循环(只读\n * 对齐,不改求值器):首个未闭合 `{{` 起剩余全字面、片段内容 trim、`{{}}` 不嵌套\n * (首个 `}}` 收口,内容含 `{{`)、片段外字面直通;空片段/路径空段 =\n * `expression_syntax_error` 信号(与求值器同码,经 parseExpressionPath 判定)。\n *\n * 与求值器的一处表示分叉(编辑器侧补全所需):未闭合 `{{` 在求值期按字面直通,\n * 在扫描结果里另立 **open 片段**(kind: 'open')——这正是「输入 `{{` 自动弹出」的\n * 触发态(spec §3),其 expr 是待输入路径;closed 片段仍按求值语义带错误信号。\n *\n * 位置字段是 JS 码元索引(end 独占)——与 PHP 字节索引不同空间,跨端共享 fixture\n * (expression-scan v1)只钉切分内容不钉位置,位置由本端派生并由单测锁死。\n * trim 字符集对齐 PHP `trim()` 默认六字符(\" \\t\\n\\r\\0\\x0B\"),**不是** JS 的\n * `String.trim()`(后者含全角空格等 Unicode 空白,会造成双端漂移)。\n *\n * 全模块纯函数、无 DOM(editor-shared-isolation)。\n */\nimport { parseExpressionPath, type ExpressionSyntaxError } from './expressionPath'\n\n/** 字面直通段(不含任何 `{{`) */\nexport interface ExpressionScanLiteral {\n kind: 'literal'\n start: number\n end: number\n text: string\n}\n\n/** 闭合片段(含 `{{ }}` 外壳区域;raw 为外壳内原文,expr 为 trim 后路径) */\nexport interface ExpressionScanFragment {\n kind: 'fragment'\n start: number\n end: number\n raw: string\n expr: string\n /** 空片段/路径空段信号(与求值器 expression_syntax_error 同码);合法为 null */\n error: ExpressionSyntaxError | null\n}\n\n/** 未闭合 `{{` 起的输入中片段(求值期按字面;编辑器侧补全触发态) */\nexport interface ExpressionScanOpen {\n kind: 'open'\n start: number\n end: number\n /** 含 `{{` 外壳的原文(到串尾) */\n text: string\n /** `{{` 之后内容的 trim 结果(待输入路径,可能为空串) */\n expr: string\n}\n\nexport type ExpressionScanPart = ExpressionScanLiteral | ExpressionScanFragment | ExpressionScanOpen\n\nexport interface ExpressionScan {\n template: string\n /** 有序切分;parts 拼回恒等于 template(空字面一律省略) */\n parts: readonly ExpressionScanPart[]\n}\n\n/** PHP trim() 默认字符集(JS String.trim 的 Unicode 空白集与之不同,勿用);\n * 编辑器侧补全的接受手术同用此集(导出防按值复刻漂移,工单 04) */\nexport const PHP_TRIM_CHARS = ' \\t\\n\\r\\0\\x0B'\n\nfunction phpTrim(value: string): string {\n let start = 0\n let end = value.length\n while (start < end && PHP_TRIM_CHARS.includes(value[start] as string)) start += 1\n while (end > start && PHP_TRIM_CHARS.includes(value[end - 1] as string)) end -= 1\n return value.slice(start, end)\n}\n\n/** 扫描模板串的片段切分:闭合/未闭合/多片段/错误信号,语义对齐求值器扫描循环 */\nexport function scanExpressionFragments(template: string): ExpressionScan {\n const parts: ExpressionScanPart[] = []\n let pos = 0\n\n let open = template.indexOf('{{')\n while (open !== -1) {\n const close = template.indexOf('}}', open + 2)\n if (close === -1) {\n // 未闭合:剩余全字面(求值语义);编辑器侧立 open 片段供补全触发\n if (open > pos) {\n parts.push({ kind: 'literal', start: pos, end: open, text: template.slice(pos, open) })\n }\n const text = template.slice(open)\n parts.push({ kind: 'open', start: open, end: template.length, text, expr: phpTrim(text.slice(2)) })\n return { template, parts }\n }\n\n if (open > pos) {\n parts.push({ kind: 'literal', start: pos, end: open, text: template.slice(pos, open) })\n }\n const raw = template.slice(open + 2, close)\n const expr = phpTrim(raw)\n const error = parseExpressionPath(expr).ok ? null : ('expression_syntax_error' as const)\n parts.push({ kind: 'fragment', start: open, end: close + 2, raw, expr, error })\n pos = close + 2\n open = template.indexOf('{{', pos)\n }\n\n if (pos < template.length) {\n parts.push({ kind: 'literal', start: pos, end: template.length, text: template.slice(pos) })\n }\n return { template, parts }\n}\n\n/**\n * 光标所在片段判定(编辑器侧语义,PHP 无光标概念故不入跨端 fixture):\n * closed 片段含光标 ⟺ start < cursor < end(`}}` 之后即离开——输入 `}}` 关闭浮层);\n * open 片段含光标 ⟺ start < cursor <= end(串尾输入位恒在内)。\n */\nexport function expressionFragmentAtCursor(\n scan: ExpressionScan,\n cursor: number,\n): ExpressionScanFragment | ExpressionScanOpen | null {\n for (const part of scan.parts) {\n if (part.kind === 'literal') continue\n const inside = part.kind === 'open' ? cursor > part.start && cursor <= part.end : cursor > part.start && cursor < part.end\n if (inside) return part\n }\n return null\n}\n","/**\n * 对齐画布 action 族的几何核(layer-align-snap 工单 01,ADR 0011):\n * 「对齐画布/贴边/贴角」是把选中图层盒整体对齐到画布几何的通用动作——与属性\n * 面板的盒内对齐(写图层 spec 的 align 字段,移动盒内内容)是两类动作,并存\n * 不回退;不绑定任何具体图层或文档数据(专属贴角预设不做,ADR 0011)。\n *\n * 几何语义:目标盒坐标按 mode 公式确定——贴边零边距、贴角统一边距(缺省\n * ALIGN_CORNER_MARGIN_PX)、居中取精确中点;盒大于画布不钳位(领域语义:\n * position 偏移可为负、溢出摆放),公式原样落位。调用方(alignToCanvas)以\n * layerBoxAt 解析的图层盒为唯一几何来源,把「目标盒坐标 − 当前盒坐标」的位移\n * 写进 position 偏移(anchor 不动——盒坐标对 position 线性,anchorOffset 不\n * 参与差值)。全模块纯函数、无 DOM。\n */\nimport type { LayerBox } from '@hankchen/canvas'\n\n/** 对齐画布 mode:居中×2 + 贴边×4 + 贴角×4 */\nexport type AlignToCanvasMode =\n | 'left'\n | 'right'\n | 'top'\n | 'bottom'\n | 'h-center'\n | 'v-center'\n | 'corner-tl'\n | 'corner-tr'\n | 'corner-bl'\n | 'corner-br'\n\n/** alignToCanvas 的可选参数:贴角系统一边距 */\nexport interface AlignToCanvasOptions {\n /** 贴角边距(四角同一取值);缺省 ALIGN_CORNER_MARGIN_PX */\n margin?: number\n}\n\n/** 贴角缺省边距(像素,ADR 0011 钉定的通用缺省,不做每图层可配置面) */\nexport const ALIGN_CORNER_MARGIN_PX = 40\n\n/** 目标盒坐标的几何入参(LayerBox 的位置尺寸切片) */\nexport type AlignBoxGeometry = Pick<LayerBox, 'x' | 'y' | 'width' | 'height'>\n\n/**\n * mode → 对齐后的目标盒左上坐标(mode 只动一个轴时另一轴保持原值):贴边零边距\n * (left x=0、right x=画布宽−盒宽……)、居中 = (画布尺寸−盒尺寸)/2(精确值不\n * 取整)、贴角 = 统一边距。盒大于画布不钳位,负值即向反侧溢出。未知 mode 返回\n * null(运行时垃圾输入的空转面;TS 层由联合类型收窄)。\n */\nexport function alignToCanvasTarget(\n mode: AlignToCanvasMode,\n box: AlignBoxGeometry,\n canvasWidth: number,\n canvasHeight: number,\n margin: number,\n): { x: number; y: number } | null {\n switch (mode) {\n case 'left':\n return { x: 0, y: box.y }\n case 'right':\n return { x: canvasWidth - box.width, y: box.y }\n case 'top':\n return { x: box.x, y: 0 }\n case 'bottom':\n return { x: box.x, y: canvasHeight - box.height }\n case 'h-center':\n return { x: (canvasWidth - box.width) / 2, y: box.y }\n case 'v-center':\n return { x: box.x, y: (canvasHeight - box.height) / 2 }\n case 'corner-tl':\n return { x: margin, y: margin }\n case 'corner-tr':\n return { x: canvasWidth - box.width - margin, y: margin }\n case 'corner-bl':\n return { x: margin, y: canvasHeight - box.height - margin }\n case 'corner-br':\n return { x: canvasWidth - box.width - margin, y: canvasHeight - box.height - margin }\n default:\n return null\n }\n}\n","/**\n * 剪贴板内核语义(工单 14):复制/粘贴/创建副本的纯函数面。\n *\n * - 剪贴板是**会话级**(不碰 OS 剪贴板、不跨会话):条目 = 选中层子树的深拷贝\n * 快照 + 复制时点的绝对盒(粘贴落位的基准)。graph 领域类型是纯 JSON 形态\n * (往返恒等契约),深拷贝用 JSON 往返——与源文档树彻底断开引用,复制后的\n * 原件编辑不影响粘贴产物。\n * - 可复制类型 = 可落根层的类型(文/图/码/表)。行/格是容器内结构(数组序语义、\n * priority 不参与根层排序),v1 粘贴统一落根层,故不可复制;格内容(文本/图片/\n * 码)可复制——粘贴出表成为根层。\n * - 粘贴落位(alt-drag-paste 工单 02 真原位):目标盒左上 = 复制时点绝对盒 +\n * 偏移,按**根层语义**反解 position(锚点补偿用画布尺寸,见 pastePosition)——\n * 格内容的 position 是格内相对值,直接平移会落到画布左上;根层源在此式下退化\n * 为 position + 偏移。⌘V 粘贴偏移入参恒为零(重复粘贴同位叠放,递增偏移链已\n * 删):dx=0 时根层源 position 恒等原位(offset 相消)、格内容源落其视觉位置;\n * PASTE_OFFSET_PX 保留给 ⌘D/副本。z 序紧邻源层由消费面(editor.pasteFromClipboard\n * 解析 sourcePath)选择 insertRootLayerAdjacentInDraft / 退化置顶。\n * - 创建副本 = 复制态 + 固定一格偏移,**不覆盖剪贴板**(excalidraw 同款语义);\n * 连续副本基于当前选中的副本链式偏移,天然不重叠。\n */\n\nimport { anchorOffset, type Anchor, type Canvas, type Layer, type LayerBox, type LayerType } from '@hankchen/canvas'\n\nimport { resolveLayer, type LayerPath } from '../shared/layerPath'\n\n/** 粘贴/副本的位置偏移基数(场景像素,整数) */\nexport const PASTE_OFFSET_PX = 20\n\n/** 可落根层的类型(= 可复制类型):容器内的行/格除外 */\nconst ROOT_PASTEABLE_TYPES: ReadonlySet<LayerType> = new Set([\n 'TextLayer',\n 'ImageLayer',\n 'QrCodeLayer',\n 'TableLayer',\n])\n\n/** 类型是否可粘贴为根层(= 是否可复制) */\nexport function isRootPasteableType(type: LayerType): boolean {\n return ROOT_PASTEABLE_TYPES.has(type)\n}\n\n/**\n * 会话剪贴板条目(工单 14;alt-drag-paste 工单 02 收窄):源子树深拷贝 + 复制\n * 时点绝对盒(粘贴落位基准,格内容的格内相对 position 据此换算画布绝对落位)+\n * 源路径(粘贴时紧邻插入的解析凭据;已删/悬空/属他文档失配退化置顶)。\n */\nexport interface ClipboardEntry {\n layer: Layer\n sourceBox: LayerBox\n sourcePath: LayerPath\n}\n\n/** 路径处图层是否可复制(可解析且类型可落根层);剪贴板入口与菜单可用态共用 */\nexport function canCopyLayerAt(doc: Canvas, path: LayerPath): boolean {\n const layer = resolveLayer(doc, path)\n return layer !== null && isRootPasteableType(layer.type)\n}\n\n/**\n * 子树深拷贝:JSON 往返(领域类型纯 JSON 形态)。输入可能是 immer draft(复制\n * 发生在文档树读取时),JSON 序列化天然产出普通对象,与 draft/源树断开引用。\n */\nexport function cloneLayerSubtree<T extends Layer>(layer: T): T {\n return JSON.parse(JSON.stringify(layer)) as T\n}\n\n/**\n * 粘贴落位的 position 反解:目标盒左上 = 源绝对盒左上 + (dx, dy),按根层语义\n * (父盒 = 画布)回推 position = 目标 − anchorOffset(锚点, 画布尺寸, 盒尺寸)。\n * 根层源退化为 position + 偏移(offset 相消);格内容源把格内相对 position 换算\n * 成画布绝对落位——副本出现在源内容视觉位置附近而非画布左上。\n */\nexport function pastePosition(\n sourceBox: LayerBox,\n anchor: Anchor,\n docWidth: number,\n docHeight: number,\n dx: number,\n dy: number,\n): { x: number; y: number } {\n const offset = anchorOffset(anchor, docWidth, docHeight, sourceBox.width, sourceBox.height)\n return { x: sourceBox.x + dx - offset.x, y: sourceBox.y + dy - offset.y }\n}\n\n/**\n * 粘贴产物准备:深拷贝 + 按源绝对盒反解根层 position(语义见 pastePosition)。\n * 锚点保持不变(副本忠实于源),盒尺寸用复制时点的布局结果(autoHeight 文本\n * 与副本的布局结果一致)。\n */\nexport function prepareRootPaste<T extends Layer>(\n layer: T,\n sourceBox: LayerBox,\n docWidth: number,\n docHeight: number,\n dx: number,\n dy: number,\n): T {\n const copy = cloneLayerSubtree(layer)\n const position = pastePosition(sourceBox, copy.position.anchor, docWidth, docHeight, dx, dy)\n return { ...copy, position: { ...copy.position, ...position } } as T\n}\n","/**\n * 查找替换的内核纯函数(canvas-web-find-replace 工单 01,CONTEXT「查找替换」词条)。\n *\n * 扫描(scanTextMatches):沿视觉序遍历全画布未标记(expression === null)的\n * TextLayer.text,大小写敏感子串匹配——数组尾→头(hitTest 同门:视觉最上先命中)、\n * 表格递归下钻(rows/cells 尾→头、content 收尾),两形态都参与(V1 rows 格内容 +\n * 模板态 template.cells 格内容,template ⊕ rows 恰存其一);隐藏层参与(隐藏是\n * 渲染排除语义非保护语义);标记字段整字段豁免(字面前缀也不替换——改走双形态\n * 编辑器既有通道:解标→改→重标),QrCode.value / ImageLayer.src / 层名不参与。\n *\n * 替换(replaceHitsInDraft):in-draft 助手按「原文快照命中」应用——命中(path +\n * 偏移对)在事务前一次算定,同字段多命中按偏移降序写入(前面的偏移不受后面长度\n * 变化影响)、替换产物不重扫(replacement 含查询串不死循环);写值 text 直写 +\n * expression 保持 null(与文本编辑提交 updateData 同门——参与者本就未标记,\n * expression=null 副作用为零)。\n *\n * 全模块纯函数、无 DOM。\n */\nimport type { Canvas, Layer, TextLayer } from '@hankchen/canvas'\nimport type { Draft } from 'immer'\n\nimport { resolveLayer, type LayerPath } from '../shared/layerPath'\n\n/** 一处命中:字段路径 + 文本偏移对 [start, end)(同字段多命中按序不重叠) */\nexport interface FindTextHit {\n readonly path: LayerPath\n readonly start: number\n readonly end: number\n}\n\n/** 单字段收集全部命中:非重叠推进(步过命中段,\"aaa\" 查 \"aa\" 得一处) */\nfunction collectFieldHits(layer: TextLayer, path: LayerPath, query: string, out: FindTextHit[]): void {\n let from = 0\n for (;;) {\n const index = layer.text.indexOf(query, from)\n if (index === -1) return\n out.push({ path, start: index, end: index + query.length })\n from = index + query.length\n }\n}\n\n/** 视觉序下钻(与 hitTest 的 hitWalk 同一门:子层后画在上,尾→头遍历) */\nfunction collectLayerHits(layer: Layer, path: LayerPath, query: string, out: FindTextHit[]): void {\n switch (layer.type) {\n case 'TableLayer':\n // 模板态格内容参与(V1 rows 格内容 + 模板格内容两形态;XOR 恰存其一)\n if (layer.template) {\n for (let i = layer.template.cells.length - 1; i >= 0; i -= 1) {\n collectLayerHits(layer.template.cells[i]!, [...path, 'template', 'cells', i], query, out)\n }\n }\n for (let i = layer.rows.length - 1; i >= 0; i -= 1) {\n collectLayerHits(layer.rows[i]!, [...path, 'rows', i], query, out)\n }\n break\n case 'TableRowLayer':\n for (let i = layer.cells.length - 1; i >= 0; i -= 1) {\n collectLayerHits(layer.cells[i]!, [...path, 'cells', i], query, out)\n }\n break\n case 'TableRowTemplate':\n // 独立出现的行模板属非法 wire(契约禁止);宽容扫过不崩溃\n for (let i = layer.cells.length - 1; i >= 0; i -= 1) {\n collectLayerHits(layer.cells[i]!, [...path, 'cells', i], query, out)\n }\n break\n case 'TableCellLayer':\n if (layer.content) collectLayerHits(layer.content, [...path, 'content'], query, out)\n break\n case 'TextLayer':\n // 仅未标记(纯静态)字段参与——豁免判定零成本:expression === null 即静态\n if (layer.expression === null) collectFieldHits(layer, path, query, out)\n break\n case 'ImageLayer':\n case 'QrCodeLayer':\n // src(资源引用语义)/ value(URL 语义)不参与\n break\n }\n}\n\n/**\n * 全画布静态文本扫描:query 为空串恒零命中(查找条初态空转)。返回视觉序命中\n * 列表(同字段多命中按偏移升序),每次从 doc 现算、不驻留(免失配维护)。\n */\nexport function scanTextMatches(doc: Canvas, query: string): FindTextHit[] {\n const hits: FindTextHit[] = []\n if (query === '') return hits\n for (let i = doc.layers.length - 1; i >= 0; i -= 1) {\n collectLayerHits(doc.layers[i]!, ['layers', i], query, hits)\n }\n return hits\n}\n\n/**\n * in-draft 替换:应用事务前算定的原文快照命中,返回实际应用处数。同字段命中\n * 聚组后按偏移降序写入;快照失效(路径不可解析/类型不符/偏移越界)的命中静默\n * 跳过(文档在快照后被结构编辑的防御分支)。零应用时不产生文档变更(immer 对\n * 同值写不记 patch,事务空转不进历史)。\n */\nexport function replaceHitsInDraft(\n draft: Draft<Canvas>,\n hits: readonly FindTextHit[],\n replacement: string,\n): number {\n const byPath = new Map<string, FindTextHit[]>()\n for (const hit of hits) {\n const key = hit.path.map(String).join('\\u0000')\n const group = byPath.get(key)\n if (group) group.push(hit)\n else byPath.set(key, [hit])\n }\n let applied = 0\n for (const group of byPath.values()) {\n const layer = resolveLayer(draft, group[0]!.path) as Draft<TextLayer> | null\n if (!layer || layer.type !== 'TextLayer') continue\n let text = layer.text\n for (const { start, end } of [...group].sort((a, b) => b.start - a.start)) {\n if (start < 0 || end > text.length || start > end) continue\n text = text.slice(0, start) + replacement + text.slice(end)\n applied += 1\n }\n layer.text = text\n layer.expression = null\n }\n return applied\n}\n","/**\n * 图层面板的内核语义(工单 10)。\n *\n * 顺序映射(spec「Vue 绑定与面板」拍板,防反直觉):\n * - 渲染顺序 = graph.layers 数组序 = priority 降序(数组头先画垫底,越大越垫底);\n * - 面板从上到下 = 数组逆序:面板顶 = 视觉最上层 = 数组尾 = 最小 priority。\n *\n * 两套重排语义分立:\n * - 根层重排走 priority 中点插值(保持「数组按 priority 降序」的契约语义,\n * 保存再打开顺序不变);插到上下邻之间 → priority = ⌊(上邻 + 下邻) / 2⌋,\n * 置顶 = min − 1、置底 = max + 1;priority 恒为整数(wire 解码 intval 同门,\n * 小数会在保存→打开时漂移),无整数间隙/同值冲突时对全表做一次按视觉序的\n * 带间隙阶梯归一化重赋(patch 大,仅兜底)。\n * - 表格行重排直接改 rows 数组序(行/格/内容是嵌套数组序语义,绘制按序遍历,\n * priority 字段在容器内不参与排序)。\n *\n * 全模块纯函数、无 DOM;draft 侧变换经 immer 事务落库,查询侧只读。\n */\nimport type { Canvas, Layer, LayerType, TableLayer, TableCellLayer, TextLayer, TableRowLayer, TableRowTemplateLayer } from '@hankchen/canvas'\nimport type { Draft } from 'immer'\n\nimport { isLayerPath, isLockedPath, resolveLayer, type LayerPath } from '../shared/layerPath'\nimport { scanExpressionFragments } from '../shared/expressionScan'\n\n/** 图层在容器树里的角色(决定面板缩进与可拖动性);templateRow = 行模板替身节点 */\nexport type LayerOutlineRole = 'root' | 'row' | 'cell' | 'content' | 'templateRow'\n\n/** 面板大纲节点:领域子树的纯投影(无文档写语义) */\nexport interface LayerOutlineNode {\n readonly type: LayerType\n readonly path: LayerPath\n readonly role: LayerOutlineRole\n /**\n * 用户命名(layer-panel-ux 工单 09):原样携带文档 name(根层重命名后由此\n * 上屏),空串 = 未命名,显示层回落派生标签。行/格/内容同字段照带(不消费)。\n */\n readonly name: string\n /**\n * 可见性(layer-panel-ux 工单 10):原样携带文档 visible,根层面板行的眼睛\n * 钮状态与隐藏行降不透明度据此投影。行/格/内容同字段照带(不消费——显隐\n * 语义只在 LayerBase 面)。\n */\n readonly visible: boolean\n /**\n * 锁定投影(canvas-web-layer-lock 工单 02):isLockedPath 前缀判定——锁定的\n * 根层及其整棵子树(行/格/内容)都携 true。锁定态住 ui 分支(会话级、零\n * wire 键),经 buildLayerOutline 的 ui 分支入参喂入;面板锁定钮状态、锁定\n * 行样式与删除钮/菜单删除项置灰据此投影(内核空转为权威,UI 呈现一致)。\n */\n readonly locked: boolean\n /**\n * 表达式前置(layer-panel-expression-prefix 工单 01):带标记内容层(三内容层\n * 且 expression !== null)的全部闭合片段按 expr 重构 `{{…}}` 外壳、单空格连接,\n * 面板零计算直读;空串 = 无前置(未标记 / 非内容层),对齐 name 字段先例;\n * 全部片段无效得常量 `{{…}}`。全角色节点照带。\n */\n readonly expressionPrefix: string\n /** TableLayer 专属:是否模板态(+行按钮置灰与 title 的判定面,spec §2.5) */\n readonly templated?: boolean\n readonly children: readonly LayerOutlineNode[]\n}\n\n/** 容器子层清单:type → (key, 孩子取法);content 单叶;template 段无下标 */\nfunction childLists(layer: Layer): { key: 'rows' | 'cells' | 'content' | 'template'; layers: readonly Layer[] }[] {\n switch (layer.type) {\n case 'TableLayer':\n // 模板态表格:大纲进模板子树(spec §2.2,推翻决策 2026-09)——行模板\n // 节点走无下标 template 段;V1 表照旧 rows\n if (layer.template !== null) return [{ key: 'template', layers: [layer.template] }]\n return [{ key: 'rows', layers: layer.rows }]\n case 'TableRowLayer':\n return [{ key: 'cells', layers: layer.cells }]\n case 'TableCellLayer':\n return layer.content ? [{ key: 'content', layers: [layer.content] }] : []\n case 'ImageLayer':\n case 'TextLayer':\n case 'QrCodeLayer':\n return []\n case 'TableRowTemplate':\n // 行模板的格进大纲(spec §2.2):模板行是链上正式一级,格操作与 V1 同心智\n return [{ key: 'cells', layers: layer.cells }]\n }\n}\n\n/** 三内容层的数据表达式标记读取面:非内容层无 data 字段,恒 null */\nfunction markedExpressionOf(layer: Layer): string | null {\n switch (layer.type) {\n case 'TextLayer':\n case 'ImageLayer':\n case 'QrCodeLayer':\n return layer.expression\n default:\n return null\n }\n}\n\n/** 全部闭合片段无效时的前置占位(spec 决策 2:无闭合片段 / expr 全空) */\nconst EXPRESSION_PREFIX_PLACEHOLDER = '{{…}}'\n\n/**\n * 表达式前置计算(layer-panel-expression-prefix 工单 01,spec 决策 2/3):判据看\n * 层型 + 标记、不看 role——实例行格内容与行模板子树格内容同享。取全部闭合片段,\n * 按 expr(phpTrim 后路径,`row.` 前缀原样)重构外壳、单空格连接;expr 空的片段\n * 无效,全部无效得常量占位。纯函数、无 I/O、不回写文档。\n */\nfunction expressionPrefixFor(layer: Layer): string {\n const expression = markedExpressionOf(layer)\n if (expression === null) return ''\n const exprs = scanExpressionFragments(expression).parts.flatMap((part) =>\n part.kind === 'fragment' && part.expr !== '' ? [part.expr] : [],\n )\n if (exprs.length === 0) return EXPRESSION_PREFIX_PLACEHOLDER\n return exprs.map((expr) => `{{${expr}}}`).join(' ')\n}\n\nfunction outlineWalk(\n layer: Layer,\n path: LayerPath,\n role: LayerOutlineRole,\n lockedPaths: readonly LayerPath[],\n): LayerOutlineNode {\n const children: LayerOutlineNode[] = []\n for (const { key, layers } of childLists(layer)) {\n for (let i = 0; i < layers.length; i += 1) {\n const roleOfChild: LayerOutlineRole =\n key === 'rows' ? 'row' : key === 'cells' ? 'cell' : key === 'template' ? 'templateRow' : 'content'\n // content 与 template 段无下标(路径语法 [..., 'template'] / [..., 'cells', k, 'content'])\n const childPath: LayerPath =\n key === 'content'\n ? [...path, 'content']\n : key === 'template'\n ? [...path, 'template']\n : [...path, key, i]\n children.push(outlineWalk(layers[i]!, childPath, roleOfChild, lockedPaths))\n }\n }\n return {\n type: layer.type,\n path,\n role,\n name: layer.name,\n visible: layer.visible,\n locked: isLockedPath(path, lockedPaths),\n expressionPrefix: expressionPrefixFor(layer),\n ...(layer.type === 'TableLayer' ? { templated: layer.template !== null } : null),\n children,\n }\n}\n\n/**\n * 面板大纲:根层按视觉层级排列(数组逆序——面板顶部 = 视觉最上层 = 数组尾),\n * 表格内行/格/内容保持数组序(行 0 在视觉顶部、格 0 在左)。\n * lockedPaths 为 ui 分支入参(canvas-web-layer-lock 工单 02):锁定态不写 graph,\n * 投影按 isLockedPath 前缀判定携带到锁定子树全树;缺省 [](无锁投影)。\n */\nexport function buildLayerOutline(\n doc: Canvas,\n lockedPaths: readonly LayerPath[] = [],\n): readonly LayerOutlineNode[] {\n const roots: LayerOutlineNode[] = []\n for (let i = doc.layers.length - 1; i >= 0; i -= 1) {\n roots.push(outlineWalk(doc.layers[i]!, ['layers', i], 'root', lockedPaths))\n }\n return roots\n}\n\n// ---- 新增图层:编辑器缺省形态 ----\n\n/**\n * 新层样式(text-defaults 工单 01,CONTEXT「新层样式」词条):记忆「下一个新建\n * 文本层用什么文本样式族」的偏好槽形态——样式粘贴文本族同门的五字段(不含\n * angle,旋转角不是新层偏好)。字段与 TextLayer 顶层字段/shape.lineHeight 对齐。\n */\nexport interface NextTextStyle {\n font: string\n fontSize: number\n fontColor: string\n autowrap: boolean\n lineHeight: number\n}\n\n/** 文本预设档位(CONTEXT「文本预设」词条):heading 40 / subheading 28 / body 16 */\nexport type TextPresetId = 'heading' | 'subheading' | 'body'\n\n/** 文本预设常量(spec 决策 4):v1 只定字号——字体缺口由新层样式槽补 */\nexport const TEXT_PRESETS: readonly { id: TextPresetId; fontSize: number }[] = [\n { id: 'heading', fontSize: 40 },\n { id: 'subheading', fontSize: 28 },\n { id: 'body', fontSize: 16 },\n]\n\n/** 新层样式槽初值:自工厂 TextLayer 形态派生(工厂缺省是单一事实源,槽跟着走) */\nexport function defaultNextTextStyle(): NextTextStyle {\n const base = createDefaultLayer('TextLayer') as TextLayer\n return {\n font: base.font,\n fontSize: base.fontSize,\n fontColor: base.fontColor,\n autowrap: base.autowrap,\n lineHeight: base.shape.lineHeight,\n }\n}\n\n/**\n * 新层样式字段的宽容读逐字段收口(text-defaults 工单 01;评审合流——捕获与\n * prefs 恢复共用同一谓词,不再双写):合法字段进合并集,缺字/类型不符忽略\n * (调用方保留现值),未知键忽略。autowrap 严格布尔(票 01 口径「不符跳过」,\n * 不做真值强转);lineHeight 须为正有限数。载荷来自属性写入与宿主 JSON 快照,\n * 类型层信任不了,运行时验。\n */\nexport function pickNextTextStylePatch(raw: unknown): Partial<NextTextStyle> {\n const out: Partial<NextTextStyle> = {}\n if (raw === null || typeof raw !== 'object') return out\n const source = raw as Record<string, unknown>\n if (typeof source.font === 'string') out.font = source.font\n if (typeof source.fontSize === 'number' && Number.isFinite(source.fontSize)) out.fontSize = source.fontSize\n if (typeof source.fontColor === 'string') out.fontColor = source.fontColor\n if (typeof source.autowrap === 'boolean') out.autowrap = source.autowrap\n if (typeof source.lineHeight === 'number' && Number.isFinite(source.lineHeight) && source.lineHeight > 0) {\n out.lineHeight = source.lineHeight\n }\n return out\n}\n\n/**\n * 编辑器新建图层的缺省形态:可见尺寸(0×0 无法命中/抓取)、top-left 摆位、\n * 中性配色。与 wire 解码缺省是两回事(那是「手写 JSON 宽进」语义),这里是\n * 编辑器 UX 语义;priority 统一由 addRootLayer 按「置顶 min−1」赋值。\n *\n * textDefaults(text-defaults 工单 01):新层样式槽覆盖参,仅 TextLayer 臂逐\n * 字段生效(其余层型忽略)——只有画拉 endCreate 与面板新增 addRootLayer 两个\n * 入口传槽;模板表/表格结构的工厂调用不传,保持确定性缺省(spec 决策 3)。\n */\nexport function createDefaultLayer(type: LayerType, textDefaults?: Partial<NextTextStyle>): Layer {\n const base = {\n // name/visible 缺省态(layer-panel-ux 工单 01):未命名 + 可见,wire 键省略\n name: '',\n visible: true,\n priority: 0,\n shape: {\n width: 0,\n height: 0,\n autoWidth: false,\n autoHeight: false,\n lineHeight: 1.2,\n padding: { top: 0, bottom: 0, left: 0, right: 0 },\n border: { top: null, bottom: null, left: null, right: null },\n backgroundColor: null,\n },\n align: { horizontal: 'left', vertical: 'top' } as const,\n position: { anchor: 'top-left', x: 0, y: 0 } as const,\n }\n switch (type) {\n case 'TextLayer': {\n const layer: TextLayer = {\n ...base,\n type,\n shape: {\n ...base.shape,\n width: 200,\n height: 60,\n lineHeight: textDefaults?.lineHeight ?? base.shape.lineHeight,\n },\n text: '文本',\n expression: null,\n font: textDefaults?.font ?? '',\n fontSize: textDefaults?.fontSize ?? 24,\n fontColor: textDefaults?.fontColor ?? '#111827',\n angle: 0,\n autowrap: textDefaults?.autowrap ?? false,\n }\n return layer\n }\n case 'ImageLayer':\n return { ...base, type, shape: { ...base.shape, width: 200, height: 150 }, src: null, expression: null }\n case 'QrCodeLayer':\n return { ...base, type, shape: { ...base.shape, width: 120, height: 120 }, value: 'canvas-web', expression: null }\n case 'TableLayer':\n return { ...base, type, shape: { ...base.shape, width: 400, height: 120 }, template: null, rowsPath: '', rows: [] }\n case 'TableRowLayer':\n return { ...base, type, shape: { ...base.shape, width: 400, height: 60 }, cells: [] }\n case 'TableRowTemplate':\n // 行模板不单卖(不进新建菜单)——只随 createTemplateTable 装配;工厂臂\n // 为穷举完整性兜底\n return { ...base, type, shape: { ...base.shape, width: 400, height: 60 }, cells: [] }\n case 'TableCellLayer':\n return { ...base, type, shape: { ...base.shape, width: 200, height: 60 }, content: null }\n }\n}\n\n/**\n * 模板表工厂(spec §2.1):V1 表缺省形态(400×120)+ 行模板装配——auto 行(声明\n * 高按解码归一为 0、宽 := 表宽关 autoWidth)+ 满宽 60 高缺省格带缺省文本内容\n * (内容宽度同步、零高度耦合,ADR 0006)。rowsPath 由调用方必填(解码\n * rows_path_missing 硬约束,创建表单校验前置);缺省格 60 高保证空态预览行可见\n * 可点(合成高 = 最高格,03 票 E1 机制)。\n */\nexport function createTemplateTable(options: { rowsPath: string }): TableLayer {\n const table = createDefaultLayer('TableLayer') as TableLayer\n const templateBase = createDefaultLayer('TableRowTemplate') as TableRowTemplateLayer\n const cellBase = createDefaultLayer('TableCellLayer') as TableCellLayer\n const contentBase = createDefaultLayer('TextLayer') as TextLayer\n\n const cell: TableCellLayer = {\n ...cellBase,\n shape: { ...cellBase.shape, width: table.shape.width },\n content: { ...contentBase, shape: { ...contentBase.shape, width: table.shape.width, autoWidth: false } },\n }\n const template: TableRowTemplateLayer = {\n ...templateBase,\n shape: { ...templateBase.shape, width: table.shape.width, autoWidth: false, autoHeight: true, height: 0 },\n cells: [cell],\n }\n return { ...table, template, rows: [], rowsPath: options.rowsPath }\n}\n\n// ---- draft 侧结构变换(EditorSession 的 action 在 transact 内调用) ----\n\n/**\n * 「insert-before 原始序号 to」移动的公共守卫与落点折算:from/to 须为安全整数、\n * from ∈ [0, n)、to ∈ [0, n];to ∈ {from, from+1}(原位/相邻落点)为无操作。\n * 返回摘除自身后的插入下标;null = 无操作(n < 2 时任何入参都落入无操作集)。\n * 工单 12 的同行格重排复用同一守卫(与行重排同款语义)。\n */\nexport function moveGuard(n: number, from: number, to: number): number | null {\n if (!Number.isSafeInteger(from) || !Number.isSafeInteger(to)) return null\n if (from < 0 || from >= n || to < 0 || to > n) return null\n if (to === from || to === from + 1) return null\n return to <= from ? to : to - 1\n}\n\n/**\n * 根层重排(draft 原位变换):面板坐标(0 = 视觉最上)折算数组坐标做 splice,\n * 再对移动层做 priority 中点插值。from 为被拖面板序号,to 为「insert-before\n * 原始面板序号」落点(∈ [0, N];to = N 即面板底);to ∈ {from, from+1} 或越界\n * 为无操作。数组序全程保持 priority 降序(渲染/解码同源的不变量)。\n * 返回移动的 (from, to) 数组下标(供选择路径重映射);null = 无变化。\n */\nexport function moveRootLayerInDraft(\n draft: Draft<Canvas>,\n fromPanel: number,\n toPanel: number,\n): { from: number; to: number } | null {\n const layers = draft.layers\n const n = layers.length\n // 面板坐标 0 = 视觉最上 = 数组尾:数组下标 = n − 1 − 面板序号(含摘除后的落点折算)\n const reduced = moveGuard(n, fromPanel, toPanel)\n if (reduced === null) return null\n const arrFrom = n - 1 - fromPanel\n const arrTo = n - 1 - reduced\n\n const [movedRaw] = layers.splice(arrFrom, 1)\n const moved = movedRaw as Draft<Layer>\n layers.splice(arrTo, 0, moved)\n\n // 插值:落点的下邻(数组左,视觉垫底侧,priority 更大)/上邻(数组右,视觉最上\n // 侧,更小)——中点/置顶/置底与「无间隙归一化兜底」的门规见 midpointPriority。\n const below = arrTo > 0 ? layers[arrTo - 1]!.priority : null\n const above = arrTo < n - 1 ? layers[arrTo + 1]!.priority : null\n const priority = midpointPriority(below, above)\n\n if (priority !== null) {\n moved.priority = priority\n } else {\n normalizeRootPriorities(layers)\n }\n return { from: arrFrom, to: arrTo }\n}\n\n/**\n * 根层落点的 priority 折算(中点插值门规,moveRootLayerInDraft 与\n * insertRootLayerAdjacentInDraft 共用):below/above 为落点两侧邻层的 priority\n * (数组左 = 视觉垫底侧、值更大;数组右 = 视觉最上侧、值更小)。有整数间隙\n * (below − above ≥ 2)取整数中点;无间隙返回 null(调用方全表归一化兜底);\n * 缺一侧退化:无 above(落点视觉最顶)取 below − 1、无 below(落点视觉垫底)\n * 取 above + 1。priority 恒整数——wire 解码按 PHP intval 语义取整,小数在\n * 保存→打开时值漂移、往返恒等即破(工单 13)。两侧邻层至少存在其一(调用方\n * 保证 n ≥ 2 / 源层在场)。\n */\nfunction midpointPriority(below: number | null, above: number | null): number | null {\n if (below !== null && above !== null) {\n return below - above >= 2 ? Math.floor((below + above) / 2) : null\n }\n if (below !== null) return below - 1\n return above === null ? null : above + 1\n}\n\n/**\n * 全表归一化:按数组序(头=垫底)重赋带间隙的整数阶梯((N−1−i) × GAP),\n * 恢复整数间隔并为后续中点插值留余量;仅作「无整数间隙」时的兜底。\n */\nconst ROOT_PRIORITY_GAP = 1024\n\nfunction normalizeRootPriorities(layers: Draft<Layer>[]): void {\n for (let i = 0; i < layers.length; i += 1) {\n layers[i]!.priority = (layers.length - 1 - i) * ROOT_PRIORITY_GAP\n }\n}\n\n/**\n * 表格行重排(draft 原位变换):直接改 rows 数组序——容器内是嵌套数组序语义,\n * 行的 priority 不参与排序、不插值(与根层重排语义分立)。from/to 为行序号\n * (行 0 在视觉顶部),to 为「insert-before 原始序号」落点 ∈ [0, rows.length]。\n * 返回 (from, to)(to 为插入后最终下标,供路径重映射);null = 无变化/入参不合法。\n */\nexport function moveTableRowInDraft(\n draft: Draft<Canvas>,\n tablePath: LayerPath,\n fromRow: number,\n toRow: number,\n): { from: number; to: number } | null {\n const table = resolveLayer(draft, tablePath)\n if (!table || table.type !== 'TableLayer') return null\n const rows = table.rows as Draft<TableRowLayer>[]\n const reduced = moveGuard(rows.length, fromRow, toRow)\n if (reduced === null) return null\n\n const [moved] = rows.splice(fromRow, 1)\n rows.splice(reduced, 0, moved!)\n return { from: fromRow, to: reduced }\n}\n\n/**\n * 新增根层(draft 原位变换):工厂缺省形态 + priority = min − 1(空画布取 0),\n * push 到数组尾(视觉最上层)。返回新层的数组下标。textDefaults(text-defaults\n * 工单 01)可选透传工厂覆盖——session 侧 addRootLayer 传新层样式槽,其余调用\n * 方不传走纯工厂缺省。\n */\nexport function addRootLayerInDraft(draft: Draft<Canvas>, type: LayerType, textDefaults?: Partial<NextTextStyle>): number {\n return insertRootLayerInDraft(draft, createDefaultLayer(type, textDefaults) as Draft<Layer>)\n}\n\n/**\n * 根层插入(draft 原位变换,工单 14 剪贴板粘贴共用):priority = min − 1(空画布\n * 取 0)+ push 到数组尾(视觉最上层)。数组序保持 priority 降序的解码不变量\n * (min−1 严格小于现最小值)。调用方负责传入的图层形态(如粘贴子树的深拷贝 +\n * 位置偏移)。返回新层的数组下标。\n */\nexport function insertRootLayerInDraft(draft: Draft<Canvas>, layer: Draft<Layer>): number {\n let min = Number.POSITIVE_INFINITY\n for (const existing of draft.layers) min = Math.min(min, existing.priority)\n layer.priority = Number.isFinite(min) ? min - 1 : 0\n draft.layers.push(layer)\n return draft.layers.length - 1\n}\n\n/**\n * 根层紧邻插入(draft 原位变换,alt-drag-paste 工单 01 原语,⌘V 粘贴工单 02 共用):\n * 新层 splice 到源层后一格 = 源层视觉上方一格,priority 取源与其视觉上一层(数组\n * 序 +1 位)之间的中点插值——数组序全程保持 priority 降序的解码不变量,整数插值\n * 同 moveRootLayerInDraft 门规(midpointPriority:无整数间隙时全表归一化兜底);\n * 源为视觉最顶/数组尾时取 source.priority − 1(置顶 min−1 同门)。\n * source 按根下标或路径寻址:路径取根祖先段(path[1],格内容等子树源紧邻其根层)。\n * 返回新层的数组下标;源下标越界/路径无数字根段返回 null(退化语义归调用方——\n * ⌘V 的「源不可解析退化置顶」在消费面回落 insertRootLayerInDraft)。\n */\nexport function insertRootLayerAdjacentInDraft(\n draft: Draft<Canvas>,\n layer: Draft<Layer>,\n source: number | LayerPath,\n): number | null {\n const rootIndex = typeof source === 'number' ? source : source[1]\n if (typeof rootIndex !== 'number' || !Number.isSafeInteger(rootIndex)) return null\n const layers = draft.layers as Draft<Layer>[]\n if (rootIndex < 0 || rootIndex >= layers.length) return null\n\n // 落点两侧:below = 源层(数组左),above = 视觉上一层(数组右),缺 above 即\n // 源为视觉最顶/数组尾(midpointPriority 内退化为 source − 1)\n const sourcePriority = layers[rootIndex]!.priority\n const above = rootIndex + 1 < layers.length ? layers[rootIndex + 1]!.priority : null\n layers.splice(rootIndex + 1, 0, layer)\n const priority = midpointPriority(sourcePriority, above)\n if (priority !== null) {\n layer.priority = priority\n } else {\n normalizeRootPriorities(layers)\n }\n return rootIndex + 1\n}\n\n/** 删除定位:list = 容器列表 splice(roots/rows/cells),content = 格内容置 null */\nexport type DeletedLayerRef =\n | { kind: 'list'; containerPath: LayerPath; key: 'layers' | 'rows' | 'cells'; index: number }\n | { kind: 'content'; path: LayerPath }\n\n/**\n * 删除图层(draft 原位变换):根层连子树整删、行/格走列表 splice、格内容置 null\n * (镜像 impl 研究 §2.8「条目操作」)。路径越界/形态不符/内容已空返回 null(无操作)。\n */\nexport function deleteLayerInDraft(draft: Draft<Canvas>, path: LayerPath): DeletedLayerRef | null {\n if (!isLayerPath(path)) return null\n\n // 格内容:content 段无索引,父 cell 路径 = 去掉尾段\n if (path[path.length - 1] === 'content') {\n const cell = resolveLayer(draft, path.slice(0, -1))\n if (!cell || cell.type !== 'TableCellLayer' || cell.content === null) return null\n ;(cell as Draft<TableCellLayer>).content = null\n return { kind: 'content', path }\n }\n\n // 根层:路径 ['layers', i],尾段即数字下标\n if (path.length === 2) {\n const index = path[1]\n const layers = draft.layers as Draft<Layer>[]\n if (typeof index !== 'number' || index >= layers.length) return null\n layers.splice(index, 1)\n return { kind: 'list', containerPath: [], key: 'layers', index }\n }\n\n // 行/格:路径 (key, index) 成对收尾——尾段是下标,倒数第二段是键 'rows' | 'cells'\n const index = path[path.length - 1]\n const key = path[path.length - 2]\n if (typeof index !== 'number' || (key !== 'rows' && key !== 'cells')) return null\n\n if (key === 'rows') {\n const table = resolveLayer(draft, path.slice(0, -2))\n if (!table || table.type !== 'TableLayer' || index >= table.rows.length) return null\n ;(table.rows as Draft<TableRowLayer>[]).splice(index, 1)\n return { kind: 'list', containerPath: path.slice(0, -2), key: 'rows', index }\n }\n const row = resolveLayer(draft, path.slice(0, -2))\n // 两型 union(spec §3.2):模板格删除与 V1 格同走 cells splice;行模板替身\n // (template 收尾路径)尾段非数字在上方守卫天然拒绝\n if (!row || (row.type !== 'TableRowLayer' && row.type !== 'TableRowTemplate') || index >= row.cells.length) {\n return null\n }\n ;(row.cells as Draft<TableCellLayer>[]).splice(index, 1)\n return { kind: 'list', containerPath: path.slice(0, -2), key: 'cells', index }\n}\n","/**\n * 表格容器结构编辑的内核语义(工单 12)。\n *\n * 重建路径与 graph 解码同源:decode.ts 镜像的 addRow/addCell/addContentLayer\n * 容器副作用在这里以 draft 变换原语复现,编辑器的行/格增删移动一律经这些原语\n * 落库——「add 同步语义」与「打开 graph 的强制同步」由同一份规则描述:\n *\n * - addRow:行宽 := 表宽、autoWidth 关;\n * - addCell:行高取最高单元格(只增长不收缩),增长时固化(autoHeight 关);\n * - addContentLayer:内容宽 := 格宽、autoWidth 关;格 auto 高 → 采纳内容动态高\n * 并固化(autoHeight 关),格固定高 → 压平内容高并关内容 autoHeight。\n *\n * 解码在每次打开时重断言这些耦合(行宽=表宽、内容宽=格宽、固定格内容高=格高、\n * 行高 ≥ 最高格),因此编辑器的字段写入(updateSpec)在表格子树按同一组规则\n * 收口(canonicalizeTableSyncInDraft)——凡解码会强同步的耦合,写入后立即重断言,\n * encode→decode 往返恒等在任何编辑序列后不破;解码不强求的(行高收缩)同样\n * 不重算,「先 add 后改尺寸不重算」的既有语义在重建路径下成立。\n *\n * 带内容的格在解码后恒为固定高(auto 标志被采纳语义固化),因此格的 autoHeight\n * 切换不是裸标志写:开 = 采纳内容动态高(autowrap 文本即「行数×行高+padding」)\n * 并固化,空格 = 置标志(解码对无内容的格保留该标志,往返恒等不受影响)。\n *\n * 模板态(TableLayer V2):行模板与 rows XOR,行级结构编辑在模板态拒绝(镜像\n * PHP addRow 抛 InvalidArgumentException,编辑器取 no-op 返回 null);表宽写入\n * 对行模板做同款宽度重同步(镜像解码 setTemplate→setWidth)。模板创作 UX(spec\n * `.scratch/canvas-web-template-authoring/`):模板格的增删重排开放——加格镜像\n * PHP TableRowTemplate::addCell 的零高度耦合(ADR 0006),行模板本身不可增删,\n * 双向转换原语是唯一合法的模板态进出门;模板子树进大纲、替身路径可选中(推翻\n * 决策 2026-09,勘误见 canvas-web-table-v2 issues/02 Comments)。\n *\n * 全模块纯 draft 变换、无 DOM;与 layerPanel.ts(工单 10 根层/行重排)分立。\n */\nimport { layerHeight, lineHeightPx, textLines, type Canvas, type Layer, type TableCellLayer, type TableRowLayer, type TableRowTemplateLayer, type TextLayoutPolicies } from '@hankchen/canvas'\nimport type { Draft } from 'immer'\n\nimport { pathsEqual, resolveLayer, isTemplateSubtreePath, type LayerPath } from '../shared/layerPath'\nimport { createDefaultLayer, moveGuard } from './layerPanel'\n\ntype DraftRow = Draft<TableRowLayer>\ntype DraftCell = Draft<TableCellLayer>\ntype DraftContent = Draft<Layer>\n/** 行模板/具体行的 shape 写入面:syncRowWidth 只触 shape,两型结构兼容(工票 02) */\ntype DraftRowOrTemplate = Draft<TableRowLayer | TableRowTemplateLayer>\n\n/**\n * resolveLayer 的 draft 断言形态:immer draft 结构上兼容只读领域类型,navigate\n * 的下降逻辑同一套;返回值按 draft 断言以解除 readonly(写入侧专用)。\n */\nfunction resolveDraft(root: Draft<Canvas>, path: LayerPath): DraftContent | null {\n return resolveLayer(root, path) as DraftContent | null\n}\n\n/** 归一 -0(PHP 整数域无 -0) */\nfunction normZero(value: number): number {\n return value === 0 ? 0 : value\n}\n\n/**\n * 内容层的动态高(采纳语义的目标高度):带内容的格在规范文档里恒为固定高\n * (内容高已被压平、autoHeight 已固化),「采纳」必须按内容自身的排版参数重算\n * 动态高——TextLayer autowrap = 行数×行高+padding、单行 = 行高+padding(空文本\n * 只剩 padding);其余类型走 layerHeight(QR 按宽正方形、其余声明高)。\n */\nfunction contentDynamicHeight(content: DraftContent, policies?: TextLayoutPolicies): number {\n if (content.type === 'TextLayer') {\n const padHeight = normZero(Math.trunc(content.shape.padding.top + content.shape.padding.bottom))\n if (content.autowrap) {\n return lineHeightPx(content) * textLines(content, policies).length + padHeight\n }\n return content.text === '' ? padHeight : lineHeightPx(content) + padHeight\n }\n return layerHeight(content, policies)\n}\n\n/** addRow 副作用:行宽同步表宽并关 autoWidth(行模板重断言复用同款同步) */\nexport function syncRowWidthInDraft(row: DraftRowOrTemplate, tableWidth: number): void {\n row.shape.width = tableWidth\n row.shape.autoWidth = false\n}\n\n/**\n * 模板格内容的宽度同步(addTemplateContentLayer 镜像):只有宽度耦合,格与内容\n * 的声明高/autoHeight 原样保留(ADR 0006 高度耦合全豁免,decodeTemplateCellLayer\n * 同门)——与 V1 的压平/采纳语义分立。\n */\nfunction syncTemplateContentWidthInDraft(content: DraftContent, cell: DraftCell): void {\n content.shape.width = cell.shape.width\n content.shape.autoWidth = false\n}\n\n/** addCell 副作用:行高取最高单元格(只增长不收缩),增长时固化(关 autoHeight) */\nexport function growRowToCellInDraft(row: DraftRow, cellHeight: number): void {\n if (row.shape.height < cellHeight) {\n row.shape.height = cellHeight\n row.shape.autoHeight = false\n }\n}\n\n/**\n * addContentLayer 副作用:内容宽同步格宽并关 autoWidth;格 auto 高 → 采纳内容\n * 动态高并固化(关 autoHeight),格固定高 → 压平内容高并关内容 autoHeight。\n * layerHeight 消费文本策略注入缝(与绘制/命中同一套度量)。\n */\nexport function syncContentIntoCellInDraft(content: DraftContent, cell: DraftCell, policies?: TextLayoutPolicies): void {\n content.shape.width = cell.shape.width\n content.shape.autoWidth = false\n if (cell.shape.autoHeight) {\n cell.shape.height = contentDynamicHeight(content, policies)\n cell.shape.autoHeight = false\n } else {\n content.shape.height = cell.shape.height\n content.shape.autoHeight = false\n }\n}\n\n/** 行内最高单元格(layerHeight 语义,与解码 addCell 逐格比较同源);空行 0 */\nfunction maxCellHeight(row: DraftRow, policies?: TextLayoutPolicies): number {\n let max = 0\n for (const cell of row.cells) {\n const height = layerHeight(cell, policies)\n if (height > max) max = height\n }\n return max\n}\n\n/** 格所在行的「行高取最高格」维持(只增长不收缩);行路径悬空静默跳过 */\nfunction growRowOfCellInDraft(cellPath: LayerPath, cellHeight: number, root: Draft<Canvas>): void {\n const row = resolveDraft(root, cellPath.slice(0, -2))\n if (row && row.type === 'TableRowLayer') growRowToCellInDraft(row, cellHeight)\n}\n\n/** 缺省格 + 缺省文本内容的重建装配:格宽取末格宽(无格取 fallbackWidth)、内容同步、行高取最高格 */\nfunction buildDefaultCellIntoRowInDraft(row: DraftRow, fallbackWidth: number): DraftCell {\n const cells = row.cells as DraftCell[]\n const cell = createDefaultLayer('TableCellLayer') as DraftCell\n cell.shape.width = cells.length > 0 ? cells[cells.length - 1]!.shape.width : fallbackWidth\n const content = createDefaultLayer('TextLayer') as DraftContent\n syncContentIntoCellInDraft(content, cell)\n cell.content = content\n growRowToCellInDraft(row, cell.shape.height)\n return cell\n}\n\n/**\n * 新增行(draft 原位变换):缺省行 + 缺省格(带缺省文本内容)走完整重建路径——\n * 行宽=表宽(addRow)→ 内容宽=格宽、固定格压平内容高(addContentLayer)→\n * 行高取最高格(addCell)。首格宽取行宽(单格行铺满),后续格宽随末格(均齐\n * 直觉)。返回新行下标;路径无法解析到表返回 null。\n *\n * 模板态守卫(工票 02):目标表 template !== null 返回 null、文档零变化——\n * 镜像 PHP addRow 抛 InvalidArgumentException 的 API 误用语义,编辑器取 no-op\n * (防 template+rows 双键落 wire)。\n */\nexport function addTableRowInDraft(draft: Draft<Canvas>, tablePath: LayerPath): number | null {\n const table = resolveDraft(draft, tablePath)\n if (!table || table.type !== 'TableLayer') return null\n if (table.template !== null) return null\n const rows = table.rows as DraftRow[]\n\n const row = createDefaultLayer('TableRowLayer') as DraftRow\n syncRowWidthInDraft(row, table.shape.width)\n const cell = buildDefaultCellIntoRowInDraft(row, row.shape.width)\n row.cells = [cell]\n rows.push(row)\n return rows.length - 1\n}\n\n/**\n * 新增单元格(draft 原位变换):缺省格 + 缺省文本内容走重建路径(内容同步 + 行高\n * 取最高格),格宽取末格宽(首格取行宽)。返回新格下标;路径无法解析到行返回 null。\n */\nexport function addTableCellInDraft(draft: Draft<Canvas>, rowPath: LayerPath): number | null {\n const row = resolveDraft(draft, rowPath)\n if (!row || row.type !== 'TableRowLayer') return null\n const cells = row.cells as DraftCell[]\n const cell = buildDefaultCellIntoRowInDraft(row, row.shape.width)\n cells.push(cell)\n return cells.length - 1\n}\n\n/**\n * 模板行加格(draft 原位变换,spec §3.1):缺省格 + 缺省文本内容——格宽取末格\n * 宽(无格取模板行宽)、内容只做宽度同步(addTemplateContentLayer 镜像);格与\n * 内容的声明高/autoHeight 原样、模板行 shape 零触碰(ADR 0006 高度耦合全豁免,\n * 无任何行高耦合)。返回新格下标;路径非表/非模板态返回 null。\n */\nexport function addTemplateCellInDraft(draft: Draft<Canvas>, tablePath: LayerPath): number | null {\n const table = resolveDraft(draft, tablePath)\n if (!table || table.type !== 'TableLayer' || table.template === null) return null\n const template = table.template as Draft<TableRowTemplateLayer>\n const cells = template.cells as DraftCell[]\n const cell = createDefaultLayer('TableCellLayer') as DraftCell\n cell.shape.width = cells.length > 0 ? cells[cells.length - 1]!.shape.width : template.shape.width\n const content = createDefaultLayer('TextLayer') as DraftContent\n syncTemplateContentWidthInDraft(content, cell)\n cell.content = content\n cells.push(cell)\n return cells.length - 1\n}\n\n/**\n * V1→V2 转换(draft 原位变换,spec §2.3):末行深拷贝重标定为行模板——搬运规则\n * 为行宽 := 表宽关 autoWidth(解码 setTemplate 同门重断言,V1 行宽恒等表宽,数值\n * 不变),格宽/格高/内容 data 全部原样(V1 带内容格恒固定高,无 auto 标志残留,\n * 零特判);rows 清空(XOR)、rowsPath 落 data 域。rowsPath 空串拒绝(解码\n * rows_path_missing 硬约束的对齐兜底——创建表单校验前置,此处双保险)。返回\n * true;no-op(路径非表/已是模板态/空表/末行零格/rowsPath 空)返回 null。\n */\nexport function convertTableToTemplateInDraft(\n draft: Draft<Canvas>,\n tablePath: LayerPath,\n rowsPath: string,\n): boolean | null {\n const table = resolveDraft(draft, tablePath)\n if (!table || table.type !== 'TableLayer' || table.template !== null) return null\n if (rowsPath === '') return null\n const rows = table.rows as DraftRow[]\n const last = rows[rows.length - 1]\n if (last === undefined || last.cells.length === 0) return null\n\n // JSON 往返深拷贝(与剪贴板 cloneLayerSubtree 同式,draft 节点落为普通对象)\n const template = JSON.parse(JSON.stringify(last)) as Draft<TableRowTemplateLayer>\n template.type = 'TableRowTemplate'\n syncRowWidthInDraft(template, table.shape.width)\n\n table.rows = []\n table.template = template\n table.rowsPath = rowsPath\n return true\n}\n\n/**\n * V2→V1 逆向转换(draft 原位变换,spec §2.3):模板深拷贝重标定为单行——内容\n * data 原样(expression 标记保留,V1 字面渲染既有行为),随后按解码副作用重断言\n * V1 归一(decodeTableRowLayer/decodeTableCellLayer 同门,往返恒等要求):带内容\n * 格 auto → 采纳内容动态高并固化、固定格压平内容高(addContentLayer 语义);空\n * auto 格声明高归零(auto 标志解码归一);行高取最高格增长固化(addCell 语义,\n * 只增长不收缩)。template 置 null、rowsPath 清空(V1 不消费)。返回 true;\n * no-op(路径非表/非模板态)返回 null。\n */\nexport function convertTableToRowsInDraft(\n draft: Draft<Canvas>,\n tablePath: LayerPath,\n policies?: TextLayoutPolicies,\n): boolean | null {\n const table = resolveDraft(draft, tablePath)\n if (!table || table.type !== 'TableLayer' || table.template === null) return null\n\n const row = JSON.parse(JSON.stringify(table.template)) as DraftRow\n row.type = 'TableRowLayer'\n syncRowWidthInDraft(row, table.shape.width)\n for (const cell of row.cells as DraftCell[]) {\n if (cell.content !== null) {\n // 镜像 setCellAutoHeightInDraft 的两步归一:auto 格先采纳内容动态高并\n // 固化,再按固定格压平内容高(解码对 V1 带内容格恒归一,往返恒等要求)\n if (cell.shape.autoHeight) {\n cell.shape.height = contentDynamicHeight(cell.content, policies)\n cell.shape.autoHeight = false\n }\n syncContentIntoCellInDraft(cell.content, cell, policies)\n } else if (cell.shape.autoHeight) {\n cell.shape.height = 0\n }\n }\n if (row.shape.autoHeight) row.shape.height = 0\n growRowToCellInDraft(row, maxCellHeight(row, policies))\n\n table.template = null\n table.rowsPath = ''\n table.rows = [row]\n return true\n}\n\n/** 跨容器移动的重映射凭据:源容器纯删除 + 目标容器纯插入(from = 原长、to = 落点) */\nexport interface MovedSubtreeRef {\n /** 移动前子树根路径(选择/悬停重挂基准) */\n fromPath: LayerPath\n /** 移动后子树根路径 */\n toPath: LayerPath\n source: { containerPath: LayerPath; key: 'rows' | 'cells'; from: number; to: number }\n target: { containerPath: LayerPath; key: 'rows' | 'cells'; from: number; to: number }\n}\n\n/**\n * 同行格重排(draft 原位变换):直接改 cells 数组序——容器内嵌套数组序语义,格的\n * priority 不参与(与 moveTableRow 同款 moveGuard 语义)。from/to 为格序号(格 0\n * 在视觉左端),to 为「insert-before 原始序号」落点 ∈ [0, cells.length]。返回\n * (from, to)(to 为插入后最终下标,供路径重映射);null = 无变化/入参不合法。\n */\nexport function moveTableCellInDraft(\n draft: Draft<Canvas>,\n rowPath: LayerPath,\n fromCell: number,\n toCell: number,\n): { from: number; to: number } | null {\n const row = resolveDraft(draft, rowPath)\n // 两型 union(spec §3.2):模板格同行重排复用同一原语(模板仅一行,跨行不可达)\n if (!row || (row.type !== 'TableRowLayer' && row.type !== 'TableRowTemplate')) return null\n const cells = row.cells as DraftCell[]\n const reduced = moveGuard(cells.length, fromCell, toCell)\n if (reduced === null) return null\n\n const [moved] = cells.splice(fromCell, 1)\n cells.splice(reduced, 0, moved!)\n return { from: fromCell, to: reduced }\n}\n\n/**\n * 行跨表移动(draft 原位变换):从源表摘除、插入目标表 toIndex 处,addRow 副作用\n * 复现(行宽同步目标表宽)。toIndex ∈ [0, 目标 rows.length](insert-before 落点,\n * 不需摘除调整——目标容器不含被移行)。同表重排返回 null(moveTableRow 的\n * moveGuard 语义,调用方分流)。\n */\nexport function moveTableRowToTableInDraft(\n draft: Draft<Canvas>,\n rowPath: LayerPath,\n targetTablePath: LayerPath,\n toIndex: number,\n): MovedSubtreeRef | null {\n const sourceTablePath = rowPath.slice(0, -2)\n if (pathsEqual(sourceTablePath, targetTablePath)) return null\n const from = rowPath[rowPath.length - 1]\n if (typeof from !== 'number') return null\n const sourceTable = resolveDraft(draft, sourceTablePath)\n const targetTable = resolveDraft(draft, targetTablePath)\n if (!sourceTable || !targetTable || sourceTable.type !== 'TableLayer' || targetTable.type !== 'TableLayer') {\n return null\n }\n // 目标表模板态守卫(工票 02):移入即落 template+rows 双键,拒绝(no-op null);\n // 源表模板态 rows 恒空,下方下标守卫天然挡住,无需显式分支\n if (targetTable.template !== null) return null\n if (!Number.isSafeInteger(toIndex) || toIndex < 0 || toIndex > targetTable.rows.length) return null\n\n const sourceRows = sourceTable.rows as DraftRow[]\n if (from >= sourceRows.length) return null\n const [movedRaw] = sourceRows.splice(from, 1)\n const moved = movedRaw as DraftRow\n syncRowWidthInDraft(moved, targetTable.shape.width)\n\n const targetRows = targetTable.rows as DraftRow[]\n const finalTo = Math.min(toIndex, targetRows.length)\n targetRows.splice(finalTo, 0, moved)\n\n return {\n fromPath: rowPath,\n toPath: [...targetTablePath, 'rows', finalTo],\n source: { containerPath: sourceTablePath, key: 'rows', from, to: from },\n // 目标容器是纯插入:remap 的 from 取插入前长度(该长度不受源删除影响)\n target: { containerPath: targetTablePath, key: 'rows', from: targetRows.length - 1, to: finalTo },\n }\n}\n\n/**\n * 格跨行移动(draft 原位变换):从源行摘除、插入目标行 toIndex 处,addCell 副作用\n * 复现(目标行高取最高格)+ addContentLayer 副作用重断言(内容层归属随格,宽/高\n * 同步幂等)。toIndex ∈ [0, 目标 cells.length](insert-before 落点,不需摘除调整)。\n * 同行重排返回 null(moveTableCellInDraft 的 moveGuard 语义,调用方分流)。\n */\nexport function moveTableCellToRowInDraft(\n draft: Draft<Canvas>,\n cellPath: LayerPath,\n targetRowPath: LayerPath,\n toIndex: number,\n policies?: TextLayoutPolicies,\n): MovedSubtreeRef | null {\n const sourceRowPath = cellPath.slice(0, -2)\n if (pathsEqual(sourceRowPath, targetRowPath)) return null\n const from = cellPath[cellPath.length - 1]\n if (typeof from !== 'number') return null\n const sourceRow = resolveDraft(draft, sourceRowPath)\n const targetRow = resolveDraft(draft, targetRowPath)\n if (!sourceRow || !targetRow || sourceRow.type !== 'TableRowLayer' || targetRow.type !== 'TableRowLayer') {\n return null\n }\n if (!Number.isSafeInteger(toIndex) || toIndex < 0 || toIndex > targetRow.cells.length) return null\n\n const sourceCells = sourceRow.cells as DraftCell[]\n if (from >= sourceCells.length) return null\n const [movedRaw] = sourceCells.splice(from, 1)\n const moved = movedRaw as DraftCell\n growRowToCellInDraft(targetRow, moved.shape.height)\n if (moved.content !== null) {\n syncContentIntoCellInDraft(moved.content, moved, policies)\n }\n\n const targetCells = targetRow.cells as DraftCell[]\n const finalTo = Math.min(toIndex, targetCells.length)\n targetCells.splice(finalTo, 0, moved)\n\n return {\n fromPath: cellPath,\n toPath: [...targetRowPath, 'cells', finalTo],\n source: { containerPath: sourceRowPath, key: 'cells', from, to: from },\n target: { containerPath: targetRowPath, key: 'cells', from: targetCells.length - 1, to: finalTo },\n }\n}\n\n/**\n * 格 autoHeight 切换(draft 原位变换):开且带内容 = 采纳内容动态高并固化\n * (解码对带内容的格恒归一为固定高,标志保留会破坏往返恒等);开且空格 = 置\n * 标志并把声明高归零(解码对 auto 标志恒清零声明高,PHP setHeight('auto') 同门);\n * 关 = 清标志。采纳可能增高,行高取最高格的解码不变量随之维持(只增长不收缩)。\n * 路径无法解析到格返回 false。\n */\nexport function setCellAutoHeightInDraft(\n draft: Draft<Canvas>,\n cellPath: LayerPath,\n on: boolean,\n policies?: TextLayoutPolicies,\n): boolean {\n const cell = resolveDraft(draft, cellPath)\n if (!cell || cell.type !== 'TableCellLayer') return false\n\n if (on && cell.content !== null) {\n cell.shape.height = contentDynamicHeight(cell.content, policies)\n cell.shape.autoHeight = false\n // 固定格归一:内容高随采纳后的格高压平(解码强同步,往返恒等要求)\n syncContentIntoCellInDraft(cell.content, cell, policies)\n } else if (on) {\n cell.shape.height = 0\n cell.shape.autoHeight = true\n } else {\n cell.shape.autoHeight = false\n }\n\n growRowOfCellInDraft(cellPath, cell.shape.height, draft)\n return true\n}\n\n/**\n * 字段写入后的解码不变量重断言:解码在每次打开时强同步表格耦合,编辑器写入后\n * 立即重断言同一组关系(往返恒等的编辑器侧镜像)——\n * - 表:shape.width/autoWidth 写入 → 全部行宽重同步(行宽=表宽);模板态对模板\n * 行做同款重同步(镜像解码 setTemplate→setWidth,工票 02);\n * - 行:行宽恒归表宽(强同步字段的写入被覆写)、行高只增长到最高格;\n * - 格:内容宽/高按 addContentLayer 重同步(auto 采纳 / 固定压平)、行高取最高格;\n * - 内容:归属格的同步重断言(隐藏字段的直写被压回)。\n * key = updateSpec 的图层内字段路径;非表格子树路径原样返回(零 patch)。\n */\nexport function canonicalizeTableSyncInDraft(\n draft: Draft<Canvas>,\n path: LayerPath,\n key: readonly string[],\n policies?: TextLayoutPolicies,\n): void {\n // 内容层:尾段 'content',归属格 = 去掉尾段\n if (path[path.length - 1] === 'content') {\n const cellPath = path.slice(0, -1)\n const cell = resolveDraft(draft, cellPath)\n if (!cell || cell.type !== 'TableCellLayer' || cell.content === null) return\n // 模板格内容(决策 2026-09):宽度耦合沿用、高度耦合全豁免(行高定稿\n // 留给展开,无「行高取最高格」可重断言)\n if (isTemplateSubtreePath(path)) {\n syncTemplateContentWidthInDraft(cell.content, cell)\n return\n }\n syncContentIntoCellInDraft(cell.content, cell, policies)\n growRowOfCellInDraft(cellPath, cell.shape.height, draft)\n return\n }\n\n const containerKey = path[path.length - 2]\n if (containerKey === 'rows') {\n const row = resolveDraft(draft, path)\n if (!row || row.type !== 'TableRowLayer') return\n const table = resolveDraft(draft, path.slice(0, -2))\n if (table && table.type === 'TableLayer') syncRowWidthInDraft(row, table.shape.width)\n // auto 标志的解码归一:声明高归零(在增长判定之前——增长会合法覆写高度并固化)\n if (row.shape.autoHeight) row.shape.height = 0\n growRowToCellInDraft(row, maxCellHeight(row, policies))\n return\n }\n\n if (containerKey === 'cells') {\n const cell = resolveDraft(draft, path)\n if (!cell || cell.type !== 'TableCellLayer') return\n // 模板格(决策 2026-09):内容宽度同步照做,行高耦合全豁免\n if (isTemplateSubtreePath(path)) {\n if (cell.content !== null) syncTemplateContentWidthInDraft(cell.content, cell)\n return\n }\n if (cell.content !== null) syncContentIntoCellInDraft(cell.content, cell, policies)\n else if (cell.shape.autoHeight) cell.shape.height = 0\n growRowOfCellInDraft(path, cell.shape.height, draft)\n return\n }\n\n // 根层表:仅 shape.width/autoWidth 写入联动行宽(其余字段与行无耦合)\n if (path[0] === 'layers' && typeof path[1] === 'number' && key[0] === 'shape' && (key[1] === 'width' || key[1] === 'autoWidth')) {\n const layer = draft.layers[path[1]] as DraftContent | undefined\n if (!layer || layer.type !== 'TableLayer') return\n if (layer.shape.autoHeight) layer.shape.height = 0\n // 模板态重同步(工票 02):镜像解码 setTemplate→setWidth 的宽度耦合,保\n // encode→decode 在编辑序列后稳定;模板态 rows 恒空,下方循环天然空转\n if (layer.template !== null) syncRowWidthInDraft(layer.template, layer.shape.width)\n for (const row of layer.rows as DraftRow[]) syncRowWidthInDraft(row, layer.shape.width)\n }\n}\n","/**\n * headless 编辑器内核的 observable store。\n *\n * - doc 分支:解码后的领域画布(@hankchen/canvas 的 Canvas),唯一事实源。\n * 写入口唯一:transact —— immer produceWithPatches 产物浅替换 + patch 广播。\n * 历史(工单 08 双栈):mergeKey 相同的连续事务合并为一步(一次拖动 = 一步\n * 历史),pointerup 后 closeMerge 闭合;undo/redo 手写双栈回放 patch,\n * undo 后新事务改写 redo 栈(Figma 语义),上限 100 步、不跨会话。\n * patch 本身可 JSON 序列化(协作的将来之路,v1 不做协作)。\n * - ui 分支:视口/选择/悬停/拖动会话等易变状态,整体替换、**永不进历史**\n * (红线 3 的编辑器延伸:派生状态只住这里,不写文档)。\n * - 通知携带变更位置(scope/branch)与 patch 组,绑定层与渲染调度据此细分脏区。\n */\nimport { applyPatches, enablePatches, produceWithPatches, setAutoFreeze, type Draft, type Patch } from 'immer'\n\nimport type { Canvas, LayerBox, LayerType } from '@hankchen/canvas'\n\nimport type { Point, Rect, Viewport } from '../spatial/camera'\nimport type { Guide, SnapAxis } from '../spatial/snap'\nimport type { ResizeHandle } from '../spatial/resize'\nimport { pathsEqual, type LayerPath } from '../shared/layerPath'\nimport type { ExpressionSchemaNode } from '../shared/expressionSchema'\nimport type { ResourceStatusMap } from '../shared/resourceStatus'\nimport { defaultNextTextStyle, type NextTextStyle } from '../editing/layerPanel'\n\nenablePatches()\n// 关闭 immer 自动冻结:文档树以「不可变 + 结构共享」语义流转(引用相等即未变),\n// 该语义不依赖冻结;而绑定层(Vue)的响应式系统会把宿主对象封进 Proxy,冻结树\n// 一旦被代理封装,immer 后续 draft/freeze 读取子属性即触发 V8 Proxy 不变量报错\n// (工单 09 属性面板实测:autoFreeze 开启时面板提交必现 \"get on proxy\" 崩溃)。\n// 防误改由唯一写入口 transact 的纪律保证。\nsetAutoFreeze(false)\n\n/** 拖动会话(ui 分支):目标路径 + 起点场景坐标 + 起始 position 偏移 + 起始盒 */\nexport interface DragGesture {\n path: LayerPath\n startScene: Point\n startPosition: { x: number; y: number }\n /**\n * 拖动开始时的绝对盒(吸附求位基准,工单 01):盒坐标对 position 线性,\n * 暂定盒 = startBox + 本步位移;住会话态,文档零接触。\n */\n startBox: LayerBox\n /**\n * Alt+拖快速复制(alt-drag-paste 工单 01):beginDrag 起手一次性判定(源可\n * 复制才置位,缺省/undefined = 普通拖动)。会话内定死——中途无修饰键读数,\n * 松/按 Alt 均不影响(spec 决策 2,与「手势模式 pointerdown 一次性定死」同构)。\n */\n copy?: boolean\n /**\n * copyMode 副本尚未插入(首移未越过死区):drag.path 仍是源层、源层不动;\n * 首移越阈(ALT_DRAG_DEAD_ZONE_SCREEN_PX)单事务插入副本后置 false,path/\n * startPosition 重指向副本(换基准)。普通拖动恒 undefined。\n */\n copyPending?: boolean\n}\n\n/**\n * 缩放会话(ui 分支,工单 07):目标路径 + 八柄 + 起点场景坐标 + 起始解析盒\n * (吸附求位与几何基准,spatial/resize 消费)。会话对象住 ui 分支,文档只收\n * resizeTo 的合并事务;与 drag 会话互斥(手势起点一次性定死)。\n */\nexport interface ResizeGesture {\n path: LayerPath\n handle: ResizeHandle\n startScene: Point\n /**\n * 缩放开始时的绝对解析盒(几何/吸附基准):对缘固定、移动缘 = 起始缘 +\n * 位移;auto 标志采纳把本盒解析尺寸落地为声明值(首个位移事务)。\n */\n startBox: LayerBox\n}\n\n/**\n * 画拉建层橡皮筋会话(ui 分支,drag-create 工单 01):武装层型 + 起点场景坐标\n * + 当前点 + 橡皮筋矩形。拖拽期纯视觉——文档零变更(spec 决策 4,比先建后\n * resize 少踩零尺寸渲染/命中两坑),rect 是 createTo 求位产物(两点正规化 +\n * QR 钳方 + 吸附修正并入),overlay 直读绘制虚线矩形与 W×H 气泡;落库在\n * endCreate 单事务(返回新层路径),与 drag/resize 会话互斥。\n */\nexport interface CreateGesture {\n /** 本次画拉的层型(beginLayerCreate 时从 armedCreate 捕获,会话内定死) */\n type: LayerType\n /** 起点场景坐标(两点正规化与死区判定基准) */\n startScene: Point\n /** 当前指针场景坐标 */\n currentScene: Point\n /** 当前橡皮筋矩形(吸附修正已并入),绑定层 overlay 直读 */\n rect: Rect\n}\n\n/**\n * 文本编辑会话(ui 分支,工单 11):编辑中的文本层路径。live 文本住在绑定层的\n * textarea(非受控),提交经 commitTextEdit 一次性落文档——「每个拼音音节一个\n * undo」被会话缓冲天然避免(impl 研究 §2.4)。\n *\n * expression 是内容类型会话标志(canvas-web-expression-editing 工单 01,spec 决策\n * 2):beginTextEdit 按进入瞬间 `layer.expression !== null` 锚定的会话快照(与\n * verticalAnchorY「进入时计、编辑中稳定」同门),裁决提交去向(表达式会话含合法\n * 片段走标记写,字面会话恒字面写)与补全 enabled。住 ui 分支不进历史;面板编辑\n * 中改动 layer.expression 不回写标志(打标是显式动作,快照是提交去向的裁决依据);\n * 内容类型 pill 切换仅翻转本标志(零文档变更零历史步)。\n */\nexport interface TextEditingSession {\n path: LayerPath\n expression: boolean\n}\n\n/**\n * 查找替换会话(canvas-web-find-replace 工单 01,CONTEXT「查找替换」词条):\n * 面板开合(open)与查找/替换两词 + 命中游标(派生命中列表的偏移下标)。匹配\n * 列表派生不驻留——每次从 doc + query 现算,游标越界在读侧钳位;同文档内\n * 关开(closeFind→beginFind)保留查询词与游标。会话态族——不进历史、不写\n * graph;openDocument 换文档整体重置(换文档带旧查询词开面板反而怪)。\n */\nexport interface FindSession {\n open: boolean\n query: string\n replacement: string\n cursor: number\n}\n\n/** 查找会话缺省态(初始与 openDocument 重置同源) */\nconst initialFindSession = (): FindSession => ({ open: false, query: '', replacement: '', cursor: 0 })\n\nexport interface EditorUi {\n viewport: Viewport\n /** 当前选中图层路径(数组路径,patch path 前缀);null = 无选择 */\n selection: LayerPath | null\n /** 悬停图层路径(gizmo hover 高亮);null = 无悬停 */\n hovered: LayerPath | null\n /** 进行中的拖动会话;null = 无拖动 */\n drag: DragGesture | null\n /** 进行中的缩放会话(工单 07 八柄);null = 无缩放 */\n resize: ResizeGesture | null\n /**\n * 画拉建层武装态(drag-create 工单 01,CONTEXT「武装」词条):面板新增项/\n * 层型快捷键选定的一次性待命层型——下一次画布按下即开画拉,建层或 Esc 即\n * 解除。无粘性工具态(spec 决策 2);openDocument 重置。\n */\n armedCreate: LayerType | null\n /** 进行中的画拉建层橡皮筋会话(拖拽期纯视觉,文档零变更);null = 无 */\n create: CreateGesture | null\n /** 进行中的文本编辑会话;null = 非编辑态 */\n editing: TextEditingSession | null\n /**\n * 重命名编辑会话(layer-panel-ux 工单 09):正在行内改名的根层路径。与\n * editing 同款会话语义——住 ui 分支不进历史,提交经 commitRename 漏斗一次\n * 落文档;null = 非重命名态。\n */\n renaming: LayerPath | null\n /**\n * 属性面板锚点折叠区开合(layer-panel-ux 工票 03):默认收起,会话内记忆\n * (与 viewport 同款的面板偏好——openDocument 换文档不重置,永不进历史)。\n */\n anchorExpanded: boolean\n /**\n * 新层样式槽(text-defaults 工单 01,CONTEXT「新层样式」词条):记忆「下一\n * 个新建文本层用什么文本样式族」的编辑器级偏好——属性面板对 TextLayer 文本\n * 族字段的权威写(updateSpec 捕获)更新槽,建层(endCreate/addRootLayer)\n * 以槽值覆盖工厂缺省。与锚点折叠同款的面板偏好——openDocument 换文档不重\n * 置,永不进历史;随工程数据 prefs 桶跨会话持久(宿主通道,内核零 localStorage)。\n */\n nextTextStyle: NextTextStyle\n /**\n * 数据源 schema 声明(content-completion 工单 03/08,D2 宿主随会话注入):\n * 编译产物形状树或降级 null(根级结构性拒绝,注入期经\n * normalizeExpressionSchemaSource 一次性收口 + console 警告;局部故障不拒绝,\n * 该节点降叶子 + 注入期汇总告警一次,树其余照常服务)。只住会话态——\n * 不进 graph、不落 localStorage、不进 wire(红线 3 延伸);openDocument 换\n * 文档不重置(声明随会话,重注入/清除走同一入口)。\n */\n dataSourceSchema: ExpressionSchemaNode | null\n /**\n * 参考线(ruler-guides-snap 工单 01,ADR 0012):使用者从标尺拖出的会话级\n * 对位轴(水平/垂直、场景坐标)。零契约面——不进历史、不写 graph、保存零\n * 改动;openDocument 换文档重置(当次编辑会话语义)。\n */\n guides: readonly Guide[]\n /**\n * 标尺显隐(ruler-guides-snap 工单 01):缺省常显,⇧R 经会话 toggleRulers\n * 翻转。与视口同款的面板偏好——openDocument 换文档保留,永不进历史。\n */\n rulersVisible: boolean\n /**\n * 当次拖动命中的吸附轴(ruler-guides-snap 工单 01):dragTo 求位的副产物,\n * 供 canvas 域画贯穿吸附线——瞬时回显,松手即清空(endDrag),非拖动态为空。\n */\n snapAxes: readonly SnapAxis[]\n /**\n * 锁定根层路径集合(canvas-web-layer-lock 工单 01):会话级锁定态——锁定的\n * 根层整子树退出画布命中面,变更类动作(拖动起点/删除/微调——微调已随\n * kbd-nav 工单 02 接入同一谓词)在内核空转,属性编辑/显隐/改名/z 序等刻意通道\n * 不受限。零契约面:不进历史、不写 graph、wire 零键、渲染产物不变\n * (红线 3 同门);openDocument 换文档重置(guides 同门——锁定是文档内容\n * 防护非面板偏好,跨文档路径悬空会误锁他人)。锁定恒为根层路径,子树\n * 命中按前缀判定收口 isLockedPath。\n */\n lockedPaths: readonly LayerPath[]\n /**\n * 查找替换会话(canvas-web-find-replace 工单 01):面板开合 + 两词 + 游标的\n * 会话态族——不进历史、不写 graph,openDocument 换文档重置(guides 同门)。\n * 命中列表不在此驻留:由 editing 域扫描纯函数从 doc + query 派生(EditorSession\n * listFindMatches 查询口),overlay 高亮与查找条计数同源消费。\n */\n find: FindSession\n /**\n * 资源物化状态切片(placeholder-padding-hint 工单 02):key = 图片 src 原串、\n * value = 物化三态,宿主从渲染端物化状态机桥接注入(整体替换,散列键反解在\n * 宿主桥)。占位态判定的物化维度事实源——只住会话态:不进历史、不写 graph、\n * wire 零键(红线 3 延伸,物化状态机切片的 ui 分支落点);openDocument 换\n * 文档重置(资源态描述当次文档,跨文档旧 src 条目无意义)。\n */\n resourceStatuses: ResourceStatusMap\n}\n\n/** 一步历史:一次(或同键合并的多次)文档事务的正向/逆向 patch 组 */\nexport interface HistoryStep {\n /** 事务合并键(拖动/滑杆);null = 已闭合、不再合并 */\n mergeKey: string | null\n patches: Patch[]\n inversePatches: Patch[]\n}\n\nexport type EditorChange =\n | { scope: 'doc'; patches: Patch[]; inversePatches: Patch[] }\n | { scope: 'ui'; branch: keyof EditorUi }\n\n/**\n * doc 通知契约注记(canvas-web-layer-lock 工单 03 目验同族三处收口):openDocument\n * 整体重建 uiValue(selection/hovered/renaming/lockedPaths 等重置)但只发 doc 通知、\n * 不逐分支发 ui 通知——订阅侧凡镜像了会被 openDocument 重置的 ui 分支,必须在 doc\n * 通知里一并重读(useSelection 先例,useLayerPanel/usePropertyPanel 同门)。\n */\n\n/** undo 栈深度上限:更旧的事务被丢弃(不跨会话,会话内也只回溯有限步) */\nexport const MAX_HISTORY_STEPS = 100\n\ntype Listener = (change: EditorChange) => void\n\n/** 文档事务:收到 doc 的 immer draft,原位改字段即可 */\nexport type DocRecipe = (draft: Draft<Canvas>) => void\n\nexport interface TransactOptions {\n /** 同键连续事务合并为一步历史(拖动的每次 pointermove 传同键) */\n mergeKey?: string\n}\n\n/** 命中吸附轴内容等(拖动高频路径的短路比较,轴数至多 2) */\nfunction snapAxesEqual(a: readonly SnapAxis[], b: readonly SnapAxis[]): boolean {\n if (a === b) return true\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i += 1) {\n const x = a[i]!\n const y = b[i]!\n if (x.orientation !== y.orientation || x.position !== y.position || x.source !== y.source) return false\n }\n return true\n}\n\n/** 锁定集合内容等(逐路径值等,toggleLayerLock/重映射路径的短路比较) */\nfunction lockedPathsEqual(a: readonly LayerPath[], b: readonly LayerPath[]): boolean {\n if (a === b) return true\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i += 1) {\n if (!pathsEqual(a[i]!, b[i]!)) return false\n }\n return true\n}\n\n/** 资源物化状态内容等(逐键值等,宿主桥整体替换的短路比较——键序无关) */\nfunction resourceStatusesEqual(a: ResourceStatusMap, b: ResourceStatusMap): boolean {\n if (a === b) return true\n const aKeys = Object.keys(a)\n if (aKeys.length !== Object.keys(b).length) return false\n for (const key of aKeys) {\n if (a[key] !== b[key]) return false\n }\n return true\n}\n\n/** 新层样式内容等(五字段值等,捕获/预设写槽的短路比较) */\nfunction nextTextStyleEqual(a: NextTextStyle, b: NextTextStyle): boolean {\n return (\n a.font === b.font\n && a.fontSize === b.fontSize\n && a.fontColor === b.fontColor\n && a.autowrap === b.autowrap\n && a.lineHeight === b.lineHeight\n )\n}\n\nexport class EditorStore {\n private docValue: Canvas | null = null\n private uiValue: EditorUi = {\n viewport: { x: 0, y: 0, zoom: 1 },\n selection: null,\n hovered: null,\n drag: null,\n resize: null,\n armedCreate: null,\n create: null,\n editing: null,\n renaming: null,\n anchorExpanded: false,\n nextTextStyle: defaultNextTextStyle(),\n dataSourceSchema: null,\n guides: [],\n rulersVisible: true,\n snapAxes: [],\n lockedPaths: [],\n find: initialFindSession(),\n resourceStatuses: {},\n }\n /** undo 栈:已提交步,栈尾最新 */\n private undoSteps: HistoryStep[] = []\n /** redo 栈:被撤销步,栈尾最近一次撤销;transact 落新事务时整体改写 */\n private redoSteps: HistoryStep[] = []\n private readonly listeners = new Set<Listener>()\n\n get doc(): Canvas | null {\n return this.docValue\n }\n\n get ui(): EditorUi {\n return this.uiValue\n }\n\n /** 已提交步(undo 栈,栈尾最新);可撤销性走 canUndo */\n get history(): readonly HistoryStep[] {\n return this.undoSteps\n }\n\n get canUndo(): boolean {\n return this.undoSteps.length > 0\n }\n\n get canRedo(): boolean {\n return this.redoSteps.length > 0\n }\n\n /** 打开/替换文档:ui 选择/编辑/重命名会话与双向历史一并重置(新文档不继承旧路径/旧事务);\n * 参考线/锁定集合/查找会话/建层武装与画拉会话随当次会话清空(对位轴/文档内容防护/\n * 查询词/待命层型不跨文档),标尺显隐/锚点折叠/新层样式作为偏好保留 */\n openDocument(canvas: Canvas): void {\n this.docValue = canvas\n this.undoSteps = []\n this.redoSteps = []\n this.uiValue = {\n ...this.uiValue,\n selection: null,\n hovered: null,\n drag: null,\n resize: null,\n armedCreate: null,\n create: null,\n editing: null,\n renaming: null,\n guides: [],\n snapAxes: [],\n lockedPaths: [],\n find: initialFindSession(),\n resourceStatuses: {},\n }\n this.notify({ scope: 'doc', patches: [], inversePatches: [] })\n }\n\n /**\n * 文档事务唯一入口:produceWithPatches 求新树,无变化即空转(不进历史不通知);\n * 有变化则浅替换 doc、按 mergeKey 合并或追加 undo 栈(超限丢最旧步)、\n * 改写 redo 栈(undo 后的新事务弃用被撤销分支,Figma 语义)、广播 patch 组。\n */\n transact(recipe: DocRecipe, options: TransactOptions = {}): void {\n if (this.docValue === null) return\n const [next, patches, inversePatches] = produceWithPatches(this.docValue, recipe)\n if (patches.length === 0) return\n\n this.redoSteps = []\n const mergeKey = options.mergeKey ?? null\n const last = this.undoSteps[this.undoSteps.length - 1]\n if (mergeKey !== null && last !== undefined && last.mergeKey === mergeKey) {\n // 正向依序拼接;逆向要后事务先撤,故新组插在前\n this.undoSteps[this.undoSteps.length - 1] = {\n mergeKey,\n patches: [...last.patches, ...patches],\n inversePatches: [...inversePatches, ...last.inversePatches],\n }\n } else {\n this.undoSteps.push({ mergeKey, patches, inversePatches })\n if (this.undoSteps.length > MAX_HISTORY_STEPS) this.undoSteps.shift()\n }\n\n this.docValue = next\n this.notify({ scope: 'doc', patches, inversePatches })\n }\n\n /** 撤销最近一步:逆向 patch 回放,该步整体移入 redo 栈 */\n undo(): void {\n const step = this.undoSteps[this.undoSteps.length - 1]\n if (step === undefined || this.docValue === null) return\n this.undoSteps.pop()\n this.redoSteps.push(step)\n // 历史导航打断 mergeKey 会话:新顶步闭合,同键后续事务另起一步\n this.closeMerge()\n this.docValue = applyPatches(this.docValue, step.inversePatches)\n // 通知契约与 transact 对齐:patches = 本次生效变更,inversePatches = 再撤本次所需\n this.notify({ scope: 'doc', patches: step.inversePatches, inversePatches: step.patches })\n }\n\n /** 重做最近一次撤销:正向 patch 回放,该步移回 undo 栈 */\n redo(): void {\n const step = this.redoSteps[this.redoSteps.length - 1]\n if (step === undefined || this.docValue === null) return\n this.redoSteps.pop()\n this.undoSteps.push(step)\n this.closeMerge()\n this.docValue = applyPatches(this.docValue, step.patches)\n this.notify({ scope: 'doc', patches: step.patches, inversePatches: step.inversePatches })\n }\n\n /**\n * 闭合拖动/滑杆事务(pointerup、blur 提交,undo/redo 导航同理):最后一步的\n * mergeKey 清空后,同键的后续事务不再并入。给键名时只在键匹配时闭合\n * (防误伤其它键的开放步)。\n */\n closeMerge(mergeKey?: string): void {\n const last = this.undoSteps[this.undoSteps.length - 1]\n if (last === undefined || last.mergeKey === null) return\n if (mergeKey !== undefined && last.mergeKey !== mergeKey) return\n this.undoSteps[this.undoSteps.length - 1] = { ...last, mergeKey: null }\n }\n\n /** 视口更新:ui 分支整体替换,旧切片引用保持原值(computed 引用短路依赖此语义) */\n setViewport(viewport: Viewport): void {\n this.uiValue = { ...this.uiValue, viewport }\n this.notify({ scope: 'ui', branch: 'viewport' })\n }\n\n /** 选中:值等短路(重复点选同一层不重绘) */\n setSelection(path: LayerPath | null): void {\n this.setUiPath('selection', path)\n }\n\n /** 悬停:值等短路(指针在同一层内移动不重绘覆盖层) */\n setHovered(path: LayerPath | null): void {\n this.setUiPath('hovered', path)\n }\n\n /** 路径类 ui 切片的共同形状:值等短路 → 整体替换 → 分支通知 */\n private setUiPath(branch: 'selection' | 'hovered' | 'renaming', path: LayerPath | null): void {\n if (pathsEqual(this.uiValue[branch], path)) return\n this.uiValue = { ...this.uiValue, [branch]: path }\n this.notify({ scope: 'ui', branch })\n }\n\n /** 拖动会话开始/结束(null);会话对象住 ui 分支,文档只收位置事务 */\n setDrag(gesture: DragGesture | null): void {\n this.uiValue = { ...this.uiValue, drag: gesture }\n this.notify({ scope: 'ui', branch: 'drag' })\n }\n\n /** 缩放会话开始/结束(null,工单 07);会话对象住 ui 分支,文档只收缩放事务 */\n setResize(gesture: ResizeGesture | null): void {\n this.uiValue = { ...this.uiValue, resize: gesture }\n this.notify({ scope: 'ui', branch: 'resize' })\n }\n\n /** 建层武装置/解除(null,drag-create 工单 01);标量值等短路(重复武装同型不通知) */\n setArmedCreate(type: LayerType | null): void {\n if (this.uiValue.armedCreate === type) return\n this.uiValue = { ...this.uiValue, armedCreate: type }\n this.notify({ scope: 'ui', branch: 'armedCreate' })\n }\n\n /** 画拉橡皮筋会话开始/更新/结束(null);会话对象住 ui 分支,落库在 endCreate 单事务 */\n setCreate(gesture: CreateGesture | null): void {\n this.uiValue = { ...this.uiValue, create: gesture }\n this.notify({ scope: 'ui', branch: 'create' })\n }\n\n /** 文本编辑会话开始/结束(null);会话住 ui 分支,文本经 commitTextEdit 一次性落文档 */\n setEditing(session: TextEditingSession | null): void {\n this.uiValue = { ...this.uiValue, editing: session }\n this.notify({ scope: 'ui', branch: 'editing' })\n }\n\n /** 重命名会话开始/结束(null);路径类切片,值等短路(重复 begin 同一路径不重绘) */\n setRenaming(path: LayerPath | null): void {\n this.setUiPath('renaming', path)\n }\n\n /** 锚点折叠区开合:布尔值等短路(重复点击同一态不重绘) */\n setAnchorExpanded(open: boolean): void {\n if (this.uiValue.anchorExpanded === open) return\n this.uiValue = { ...this.uiValue, anchorExpanded: open }\n this.notify({ scope: 'ui', branch: 'anchorExpanded' })\n }\n\n /**\n * 新层样式槽逐字段合并(text-defaults 工单 01:捕获/预设/prefs 恢复的统一\n * 写入口):patch 并入现值,五字段值等短路(重复写同值零通知),不进历史。\n */\n setNextTextStyle(patch: Partial<NextTextStyle>): void {\n const next = { ...this.uiValue.nextTextStyle, ...patch }\n if (nextTextStyleEqual(this.uiValue.nextTextStyle, next)) return\n this.uiValue = { ...this.uiValue, nextTextStyle: next }\n this.notify({ scope: 'ui', branch: 'nextTextStyle' })\n }\n\n /**\n * 数据源 schema 声明替换(归一产物或降级 null,校验收口在注入缝不在这里):\n * 同引用短路(重复注入同一归一产物不惊动订阅方);仅 ui 通知,不参与重绘\n * 脏标(onStoreChange 对该分支短路)。\n */\n setDataSourceSchema(schema: ExpressionSchemaNode | null): void {\n if (this.uiValue.dataSourceSchema === schema) return\n this.uiValue = { ...this.uiValue, dataSourceSchema: schema }\n this.notify({ scope: 'ui', branch: 'dataSourceSchema' })\n }\n\n /** 参考线列表替换(增删动作在会话门面收口);仅 ui 通知,不参与历史 */\n setGuides(guides: readonly Guide[]): void {\n this.uiValue = { ...this.uiValue, guides }\n this.notify({ scope: 'ui', branch: 'guides' })\n }\n\n /** 标尺显隐:布尔值等短路(重复置同态不重绘) */\n setRulersVisible(visible: boolean): void {\n if (this.uiValue.rulersVisible === visible) return\n this.uiValue = { ...this.uiValue, rulersVisible: visible }\n this.notify({ scope: 'ui', branch: 'rulersVisible' })\n }\n\n /** 当次命中吸附轴替换(dragTo 副产物 / endDrag 清空);内容等短路(未命中步不重绘) */\n setSnapAxes(axes: readonly SnapAxis[]): void {\n if (snapAxesEqual(this.uiValue.snapAxes, axes)) return\n this.uiValue = { ...this.uiValue, snapAxes: axes }\n this.notify({ scope: 'ui', branch: 'snapAxes' })\n }\n\n /** 锁定集合替换(toggleLayerLock 收口,翻转逻辑在会话门面);内容等短路(集合未变不重绘) */\n setLockedPaths(paths: readonly LayerPath[]): void {\n if (lockedPathsEqual(this.uiValue.lockedPaths, paths)) return\n this.uiValue = { ...this.uiValue, lockedPaths: paths }\n this.notify({ scope: 'ui', branch: 'lockedPaths' })\n }\n\n /**\n * 查找会话替换(beginFind/closeFind/输入写入经会话门面收口到这里);内容等\n * 短路(重复开合/同词写入不通知)。仅 ui 通知,不参与历史。\n */\n setFind(find: FindSession): void {\n const current = this.uiValue.find\n if (\n current.open === find.open\n && current.query === find.query\n && current.replacement === find.replacement\n && current.cursor === find.cursor\n ) {\n return\n }\n this.uiValue = { ...this.uiValue, find }\n this.notify({ scope: 'ui', branch: 'find' })\n }\n\n /**\n * 资源物化状态替换(placeholder-padding-hint 工单 02):宿主从渲染端物化状态机\n * 桥接注入的唯一写入口,整体替换语义;内容等短路(同态快照不惊动订阅方,\n * 桥接的高频整体替换零噪声)。仅 ui 通知,不参与历史、不写 graph(红线 3 延伸)。\n */\n setResourceStatuses(statuses: ResourceStatusMap): void {\n if (resourceStatusesEqual(this.uiValue.resourceStatuses, statuses)) return\n this.uiValue = { ...this.uiValue, resourceStatuses: statuses }\n this.notify({ scope: 'ui', branch: 'resourceStatuses' })\n }\n\n subscribe(listener: Listener): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n private notify(change: EditorChange): void {\n for (const listener of this.listeners) listener(change)\n }\n}\n","/**\n * 快捷键注册表(工单 14):键位→action 的**集中声明**。\n *\n * 注册表即数据(声明式 combo → action 条目),分类器只做匹配与让路;撤销/重做\n * 快捷键自工单 08 的 historyShortcut 并入此表(单表声明,消除双分类器)。缺省集\n * 跟随 excalidraw 惯例并按 v1 单选裁剪:工具切换/全选不可用(单选无对象)。重命\n * 名 F2 自 layer-panel-ux 工单 09 入表(分派到选中根层的重命名编辑会话)。z 序与\n * 缩放键位自 kbd-nav 工单 01 入表(⌘]/⌘[ 前移/后移、⌥⌘]/⌥⌘[ 置顶/置底——Canva/\n * Sketch 同构,弃 Excalidraw ⌘⇧ 系 Safari 换 tab 键位拦截不可靠;⌘0 复位 100%、\n * ⇧1/⇧2 适应画布/选区)。方向键微调自 kbd-nav 工单 02 入表:裸方向键 1px 基础\n * 步、⇧+方向键 10px 大步,方向 × 步长独立 action id(shift 精确匹配先例所致)。\n * 循环选层自 kbd-nav 工单 03 入表:Tab/⇧Tab 沿面板序在根层间移动选中(CONTEXT\n * 「循环选层」词条)——文本编辑与属性面板输入中的 Tab 由让路规则天然放行原生\n * 焦点移动,不劫持。查找替换自 canvas-web-find-replace 工单 01 入表:⌘F 分派\n * beginFind(⌘F 开会话归内核、Esc 关归 Vue 面板组件——内核只持会话态)。样式\n * 粘贴自 canvas-web-style-paste 工单 01 入表:⌥⌘C/⌥⌘V 复制/粘贴样式(CONTEXT\n * 「样式粘贴」词条)。\n * 条目携带 label(中文短句)/ group(展示归组)元数据:\n * 注册表是键位的唯一事实源,帮助面板(kbd-nav 工单 04)直读渲染,后续动作自动\n * 入面板。\n *\n * 让路规则(绑定层折算输入,分类器统一裁决——全部有测试锁定):\n * - 输入法合成中(isComposing || keyCode 229 折算为 composing):候选窗里的按键\n * 属输入法内部编辑;\n * - 文本编辑态(ui.editing ≠ null 折算为 editing):焦点路由进 textarea——Delete\n * 不得删图层、Ctrl/Cmd+Z 撤「输入」而非文档(原生 undo);\n * - 焦点在可编辑元素(input/textarea/select/contentEditable 折算为\n * editableTarget):属性面板/工具栏输入框的原生编辑优先(Delete 删的是输入框\n * 里的字符,不是图层)——重命名输入框内按键同样由此让路。\n *\n * 键匹配双通道:缺省按 KeyboardEvent.key(小写归一)匹配;声明 code 的条目改按\n * KeyboardEvent.code(物理键)匹配——mac ⌥ 修饰下 key 是变体字符(US 布局 ⌥]\n * 非 ']')、Shift+数字是标点变体(US 布局 ⇧1 为 '!'),key 不可靠。mod/shift/alt\n * 全部精确匹配(undo/redo 只差 shift 的先例扩展到 alt:⌘] 条目不吞 ⌥⌘] 按键,\n * 让路给 code 条目)。\n *\n * 本模块无 DOM:KeyboardEvent → 输入的折算归绑定层(useShortcuts)。\n */\nimport type { LayerType } from '@hankchen/canvas'\n\n/** 快捷键动作(会话分派面 executeShortcut 的入参域) */\nexport type EditorShortcutAction =\n | 'undo'\n | 'redo'\n | 'copy'\n | 'paste'\n | 'duplicate'\n | 'copyStyle'\n | 'pasteStyle'\n | 'delete'\n | 'rename'\n | 'findReplace'\n | 'toggleRulers'\n | 'toggleLayerLock'\n | 'toggleLayerVisibility'\n | 'bringForward'\n | 'sendBackward'\n | 'bringToFront'\n | 'sendToBack'\n | 'nudgeUp'\n | 'nudgeDown'\n | 'nudgeLeft'\n | 'nudgeRight'\n | 'nudgeUpCoarse'\n | 'nudgeDownCoarse'\n | 'nudgeLeftCoarse'\n | 'nudgeRightCoarse'\n | 'selectNextLayer'\n | 'selectPrevLayer'\n | 'armCreateText'\n | 'armCreateTable'\n | 'armCreateQrCode'\n | 'armCreateImage'\n | 'zoomReset'\n | 'fitToSurface'\n | 'fitToSelection'\n | 'helpShortcuts'\n\n/** 画拉建层武装四动作的联合(注册表条目与分派、绑定层提示路由的共用键域) */\nexport type ArmCreateShortcutAction = keyof typeof ARM_CREATE_LAYER_TYPES\n\n/**\n * 武装动作 → 层型(canvas-web-drag-create 工单 03):「哪四个动作是武装、各武装\n * 什么层型」的单一事实源——executeShortcut 分派与绑定层桥(useShortcuts 的提示\n * 路由)都从这里取,加层型只改此表与注册表条目两处。\n */\nexport const ARM_CREATE_LAYER_TYPES = {\n armCreateText: 'TextLayer',\n armCreateTable: 'TableLayer',\n armCreateQrCode: 'QrCodeLayer',\n armCreateImage: 'ImageLayer',\n} as const satisfies Record<string, LayerType>\n\n/**\n * 帮助面板的展示归组(kbd-nav 工单 04):分节渲染的键位归类。展示名归 Vue,\n * 这里只定枚举域。text 归组自 canvas-web-find-replace 工单 01 入表(查找替换\n * 等文本编辑面动作,与图层/视图操作分节)。\n */\nexport type EditorShortcutGroup = 'history' | 'clipboard' | 'layer' | 'text' | 'view' | 'help'\n\n/**\n * 键位组合声明:key 为 KeyboardEvent.key 的小写归一形态;mod/shift/alt 精确匹配。\n * 声明 code 时改按 KeyboardEvent.code(物理键)匹配(⌥/⇧ 变体字符场景),key 降级\n * 为条目自述;未声明 code 的条目按 key 匹配、code 不参与。\n */\nexport interface ShortcutCombo {\n key: string\n /** Ctrl(Windows/Linux)或 Cmd(macOS),两平台等价 */\n mod: boolean\n shift: boolean\n /** ⌥(两平台等价);缺省 false 与输入精确匹配(带 ⌥ 的按键不失配到无 ⌥ 条目) */\n alt?: boolean\n /** KeyboardEvent.code:声明即改物理键匹配(mac ⌥ 变体字符 / ⇧ 数字变体) */\n code?: string\n}\n\n/** 注册表条目:键位组合 → 动作(label/group 是帮助面板的展示元数据) */\nexport interface EditorShortcutBinding {\n readonly combo: ShortcutCombo\n readonly action: EditorShortcutAction\n /** 中文短句(帮助面板直读,如「前移一层」) */\n readonly label: string\n /** 展示归组(帮助面板分节) */\n readonly group: EditorShortcutGroup\n}\n\n/**\n * 缺省注册表。undo 与 redo 只差 shift,故 shift 精确匹配(不能忽略);⌥⌘]/⌥⌘[ 与\n * ⇧1/⇧2 按 code 匹配(变体字符),其余条目按 key 匹配。\n */\nexport const DEFAULT_EDITOR_SHORTCUTS: readonly EditorShortcutBinding[] = [\n { combo: { key: 'z', mod: true, shift: false }, action: 'undo', label: '撤销', group: 'history' },\n { combo: { key: 'z', mod: true, shift: true }, action: 'redo', label: '重做', group: 'history' },\n { combo: { key: 'y', mod: true, shift: false }, action: 'redo', label: '重做', group: 'history' },\n { combo: { key: 'c', mod: true, shift: false }, action: 'copy', label: '复制', group: 'clipboard' },\n { combo: { key: 'v', mod: true, shift: false }, action: 'paste', label: '粘贴', group: 'clipboard' },\n { combo: { key: 'd', mod: true, shift: false }, action: 'duplicate', label: '创建副本', group: 'clipboard' },\n // 样式粘贴(canvas-web-style-paste 工单 01):⌥⌘C/⌥⌘V 复制/粘贴样式(Figma/\n // Sketch 同构,行业同键位)。⌥ 修饰下 mac event.key 是变体字符(US 布局\n // ⌥C = 'ç'),key 匹配不可靠——按物理键 code 匹配(kbd-nav 括号条目同门);\n // alt 精确匹配使 ⌘C(无 ⌥)不失配到样式条目、⌥⌘C 也不吞 ⌘C(undo/redo\n // 只差 shift 的先例扩展到 alt)。浏览器 DevTools 占 ⌥⌘C 时右键菜单保底\n {\n combo: { key: 'c', mod: true, shift: false, alt: true, code: 'KeyC' },\n action: 'copyStyle',\n label: '复制样式',\n group: 'clipboard',\n },\n {\n combo: { key: 'v', mod: true, shift: false, alt: true, code: 'KeyV' },\n action: 'pasteStyle',\n label: '粘贴样式',\n group: 'clipboard',\n },\n { combo: { key: 'delete', mod: false, shift: false }, action: 'delete', label: '删除图层', group: 'layer' },\n { combo: { key: 'backspace', mod: false, shift: false }, action: 'delete', label: '删除图层', group: 'layer' },\n { combo: { key: 'f2', mod: false, shift: false }, action: 'rename', label: '重命名图层', group: 'layer' },\n // 查找替换(canvas-web-find-replace 工单 01,CONTEXT「查找替换」词条):⌘F\n // 呼出画布顶部浮动查找条(Canva 同构;浏览器页内查找可 preventDefault 拦截,\n // ⌘D 抢书签先例)。mod+shift 精确匹配先例——⇧⌘F 变体不入表\n {\n combo: { key: 'f', mod: true, shift: false },\n action: 'findReplace',\n label: '查找替换',\n group: 'text',\n },\n // 标尺显隐(ruler-guides-snap 工单 01):裸键 + shift(mod 变体不占用,\n // 不抢浏览器刷新等既有语义)\n { combo: { key: 'r', mod: false, shift: true }, action: 'toggleRulers', label: '标尺显隐', group: 'view' },\n // 锁定 ⇧⌘L / 显隐 ⇧⌘H(canvas-web-layer-lock 工单 02,显隐键位系 feature-status\n // §一挂账补位):行业趋同(Figma/Sketch 同款);mod+shift 组合不抢浏览器\n // ⌘L 地址栏、⌘H 历史页既有语义\n { combo: { key: 'l', mod: true, shift: true }, action: 'toggleLayerLock', label: '锁定/解锁图层', group: 'layer' },\n {\n combo: { key: 'h', mod: true, shift: true },\n action: 'toggleLayerVisibility',\n label: '显示/隐藏图层',\n group: 'layer',\n },\n // z 序四件套(kbd-nav 工单 01):⌘]/⌘[ 前移/后移一格,⌥⌘]/⌥⌘[ 置顶/置底\n // (Canva/Sketch 同构);⌥ 变体字符下 key 不可靠,置顶/置底按物理键 code 匹配\n { combo: { key: ']', mod: true, shift: false }, action: 'bringForward', label: '前移一层', group: 'layer' },\n { combo: { key: '[', mod: true, shift: false }, action: 'sendBackward', label: '后移一层', group: 'layer' },\n {\n combo: { key: ']', mod: true, shift: false, alt: true, code: 'BracketRight' },\n action: 'bringToFront',\n label: '置顶',\n group: 'layer',\n },\n {\n combo: { key: '[', mod: true, shift: false, alt: true, code: 'BracketLeft' },\n action: 'sendToBack',\n label: '置底',\n group: 'layer',\n },\n // 微调(kbd-nav 工单 02):裸方向键 1px 基础步、⇧+方向键 10px 大步,方向 ×\n // 步长独立 action id(shift 精确匹配先例所致,undo/redo 同款);mod/alt 修饰\n // 不入表(⌘←/→ 浏览器历史导航、⌥+方向既有语义不抢)\n { combo: { key: 'arrowup', mod: false, shift: false }, action: 'nudgeUp', label: '微调上移', group: 'layer' },\n { combo: { key: 'arrowdown', mod: false, shift: false }, action: 'nudgeDown', label: '微调下移', group: 'layer' },\n { combo: { key: 'arrowleft', mod: false, shift: false }, action: 'nudgeLeft', label: '微调左移', group: 'layer' },\n { combo: { key: 'arrowright', mod: false, shift: false }, action: 'nudgeRight', label: '微调右移', group: 'layer' },\n { combo: { key: 'arrowup', mod: false, shift: true }, action: 'nudgeUpCoarse', label: '大步上移', group: 'layer' },\n { combo: { key: 'arrowdown', mod: false, shift: true }, action: 'nudgeDownCoarse', label: '大步下移', group: 'layer' },\n {\n combo: { key: 'arrowleft', mod: false, shift: true },\n action: 'nudgeLeftCoarse',\n label: '大步左移',\n group: 'layer',\n },\n {\n combo: { key: 'arrowright', mod: false, shift: true },\n action: 'nudgeRightCoarse',\n label: '大步右移',\n group: 'layer',\n },\n // 循环选层(kbd-nav 工单 03):Tab 朝面板垫底方向、⇧Tab 反向(shift 精确\n // 匹配先例——⇧Tab 不落裸 Tab 条目);mod/alt 修饰不入表(⌘Tab/⌥Tab 浏览器\n // 与系统既有语义不抢)。文本编辑/输入框中的 Tab 由让路规则天然放行原生焦点\n // 移动,不劫持\n { combo: { key: 'tab', mod: false, shift: false }, action: 'selectNextLayer', label: '循环选下一层', group: 'layer' },\n { combo: { key: 'tab', mod: false, shift: true }, action: 'selectPrevLayer', label: '循环选上一层', group: 'layer' },\n // 画拉建层武装四条目(canvas-web-drag-create 工单 03,CONTEXT「武装」词条):\n // T/G/Q/I 层型待命——下一次画布按下即开画拉,建层或 Esc 即解除(一次性待命,\n // 无粘性工具)。按物理键 code 匹配(⇧1/⇧2 数字行同门先例):非拉丁布局的\n // key 是本地字符(西里尔布局 T 位产出 'т'),code 恒 KeyT 两边通吃;mod/shift/\n // alt 精确匹配(⌘T 新标签、⇧T、⌥T 变体不入表)。文本编辑态/输入框焦点由让路\n // 规则天然放行(编辑文本时 T 打字不武装)——绑定层 useShortcuts 桥对命中条目\n // 直写状态栏瞬时提示,不经本表\n { combo: { key: 't', mod: false, shift: false, code: 'KeyT' }, action: 'armCreateText', label: '画拉建文本层', group: 'layer' },\n { combo: { key: 'g', mod: false, shift: false, code: 'KeyG' }, action: 'armCreateTable', label: '画拉建表格', group: 'layer' },\n { combo: { key: 'q', mod: false, shift: false, code: 'KeyQ' }, action: 'armCreateQrCode', label: '画拉建二维码', group: 'layer' },\n { combo: { key: 'i', mod: false, shift: false, code: 'KeyI' }, action: 'armCreateImage', label: '画拉建图片', group: 'layer' },\n // 缩放(kbd-nav 工单 01):⌘0 复位 100%(视口中心为锚;浏览器吞键时的降级\n // 预案 = Figma 纯 shift ⇧0/1/2,宿主注入亦可覆盖);⇧1/⇧2 适应画布/选区,\n // 数字行按 code 匹配(US 布局 ⇧1 的 key 是 '!',法国布局是 '1',code 恒\n // Digit1 两边通吃)\n { combo: { key: '0', mod: true, shift: false }, action: 'zoomReset', label: '缩放复位 100%', group: 'view' },\n { combo: { key: '1', mod: false, shift: true, code: 'Digit1' }, action: 'fitToSurface', label: '适应画布', group: 'view' },\n {\n combo: { key: '2', mod: false, shift: true, code: 'Digit2' },\n action: 'fitToSelection',\n label: '适应选区',\n group: 'view',\n },\n // 帮助面板(kbd-nav 工单 04):⌘/ 开合快捷键帮助。UI 面动作——对话态是 UI\n // 关注点,绑定层桥(useShortcuts)拦截路由到 useShortcutsHelp 单例态,不经\n // 内核 dispatcher、不进内核 store;注册表是键位唯一事实源,条目在列帮助面板\n // 才渲染(group 'help' 系元数据扩展时预留的归组)\n {\n combo: { key: '/', mod: true, shift: false },\n action: 'helpShortcuts',\n label: '快捷键帮助',\n group: 'help',\n },\n]\n\n/**\n * 快捷键输入:绑定层从 KeyboardEvent 与会话状态折算(本类型不出现任何 DOM 类型)。\n * alt/code 为可选——未折算的宿主按缺省(无 ⌥、无 code)参与匹配。\n */\nexport interface EditorShortcutInput {\n /** KeyboardEvent.key(大小写随 Shift 与大小写锁定,分类器归一) */\n key: string\n mod: boolean\n shift: boolean\n /** KeyboardEvent.altKey 折算;缺省(未折算)视作 false */\n alt?: boolean\n /** KeyboardEvent.code 折算;声明 code 的注册表条目只认它,缺省(未折算)恒不命中 */\n code?: string\n /** 输入法合成中:isComposing || keyCode === 229 由绑定层折算 */\n composing: boolean\n /** 文本编辑态(ui.editing ≠ null):全部文档快捷键让路给 textarea */\n editing: boolean\n /** 焦点在可编辑元素(输入框/可编辑节点):让路给原生编辑 */\n editableTarget: boolean\n}\n\n/**\n * 键位 → 动作分类:让路规则优先(合成中/编辑态/输入态一律 null),再按注册表\n * 匹配——声明 code 的条目按 input.code 全等(mod/shift/alt 精确),其余按 key\n * 小写归一后全等。未命中返回 null。绑定集可注入(宿主扩展键位;缺省\n * DEFAULT_EDITOR_SHORTCUTS)。\n */\nexport function classifyEditorShortcut(\n input: EditorShortcutInput,\n bindings: readonly EditorShortcutBinding[] = DEFAULT_EDITOR_SHORTCUTS,\n): EditorShortcutAction | null {\n if (input.composing || input.editing || input.editableTarget) return null\n const key = input.key.toLowerCase()\n const alt = input.alt ?? false\n for (const binding of bindings) {\n const keyMatches = binding.combo.code !== undefined ? binding.combo.code === input.code : binding.combo.key === key\n if (\n keyMatches &&\n binding.combo.mod === input.mod &&\n binding.combo.shift === input.shift &&\n (binding.combo.alt ?? false) === alt\n ) {\n return binding.action\n }\n }\n return null\n}\n","/**\n * 编辑器状态切片与偏好(project-data 工单 01,spec §3/§5):内核导出的「单画布\n * 切片 + 偏好 + schemaVersion 常量」三件,EditorSession 实例方法(editor.ts)消费。\n *\n * 分层纪律(内核帧盲):帧名/帧缓冲/文档是宿主领域——frames 容器(帧名 → 切片)\n * 与完整快照聚合类型(schemaVersion/frames/prefs)归宿主拼装,宿主只透传切片\n * 内容、不解释 guides/lockedPaths 语义;本模块只出两型 + 版本常量 + restore 的\n * 轻结构校验。零契约面:切片不进 wire、不进历史栈、不是第二种文档格式——graph\n * 仍是内核唯一保存物。\n */\nimport type { Guide, GuideOrientation } from '../spatial/snap'\nimport type { LayerPath } from '../shared/layerPath'\nimport type { NextTextStyle } from '../editing/layerPanel'\n\n/** 快照格式版本:随格式演化单调递增;v1 = 1(宽容读贯彻,restore 不分支此值) */\nexport const EDITOR_STATE_SCHEMA_VERSION = 1\n\n/** 帧作用域切片:当前打开画布的编辑器状态(工程数据快照的 frames[帧名] 值) */\nexport interface CanvasEditorState {\n /**\n * 参考线(场景坐标):id 是会话内自增值、无持久语义——export 原样带出,\n * restore 端清空后逐条 addGuide 重取号,快照 id 出方向即失效。\n */\n guides: readonly Guide[]\n /** 锁定根层路径集合(随工程数据持久化,仍零 wire 面) */\n lockedPaths: readonly LayerPath[]\n}\n\n/** 编辑器级偏好:跨文档、跨会话内核自持(工程数据快照的 prefs 桶,分类学终裁三项) */\nexport interface EditorPrefsState {\n rulersVisible: boolean\n anchorExpanded: boolean\n /**\n * 新层样式槽(text-defaults 工单 01,CONTEXT「新层样式」词条):随 prefs 桶\n * 跨会话持久(per-模板记忆)。schemaVersion 不升——加法演进,旧快照缺字段\n * 由 applyEditorPrefs 宽容读兜底为现值(会话内即工厂缺省)。\n */\n nextTextStyle: NextTextStyle\n}\n\n/** 快照 guide 的恢复形:id 已剥(无持久语义),只余 addGuide 入参两字段 */\nexport interface GuidePlacement {\n orientation: GuideOrientation\n position: number\n}\n\n/** 校验产出的恢复切片:guide 已剥壳为 addGuide 入参形(快照 id 丢弃),路径外壳原样 */\nexport interface ParsedEditorStateSlice {\n guides: readonly GuidePlacement[]\n lockedPaths: readonly LayerPath[]\n}\n\n/**\n * 轻结构校验(restoreEditorState 的唯一入口):形状不合法整体拒(返 null,调用\n * 方返 false 宿主记因),合法但内容悬空不算拒——锁定路径只验「是数组」外壳,\n * 可解析性(越界/形态与文档不符)归 pruneDanglingPaths 按文档裁定。宽容读:\n * 切片与 guide 条目的未知键忽略、guide id 不校验不透传(重排重取号,快照 id 无\n * 持久语义);guides/lockedPaths 两载荷键缺一或非数组即形状不完整,整体拒。\n * 载荷来自宿主层 JSON 快照(存储端零校验),类型层的形状信任不了,运行时验。\n */\nexport function parseCanvasEditorState(state: CanvasEditorState): ParsedEditorStateSlice | null {\n const raw = state as unknown\n if (raw === null || typeof raw !== 'object') return null\n const source = raw as Record<string, unknown>\n if (!Array.isArray(source.guides) || !Array.isArray(source.lockedPaths)) return null\n const guides: GuidePlacement[] = []\n for (const entry of source.guides as unknown[]) {\n if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) return null\n const { orientation, position } = entry as Record<string, unknown>\n if (orientation !== 'horizontal' && orientation !== 'vertical') return null\n if (typeof position !== 'number' || !Number.isFinite(position)) return null\n guides.push({ orientation, position })\n }\n for (const path of source.lockedPaths as unknown[]) {\n if (!Array.isArray(path)) return null\n }\n return { guides, lockedPaths: source.lockedPaths as readonly LayerPath[] }\n}\n","/**\n * 字体清单(工单 13):编辑会话级的字体可选清单——「清单 ≠ 物化」,core 只持有\n * 引用与展示名,字体的加载仍走渲染端物化管线(FontFace 注册),本模块零 I/O。\n *\n * - 内置清单:宿主在创建会话时配置(URL 列表可配置;缺省为空——宿主不配置即\n * 只有内置默认字体与自定义上传项)。\n * - 自定义字体:上传动作(uploadFont)成功后追加,会话内即可选可用;清单是\n * 会话状态不是文档数据,不进 graph(往返恒等契约禁止私加字段),保存/打开\n * 不携带。\n */\nexport interface FontCatalogEntry {\n /** 展示名(清单选择器显示;与引用解耦,URL 不直接暴露给编辑者) */\n readonly label: string\n /** 字体引用(URL / data URL),与 TextLayer.font 同域;物化管线按引用装载 */\n readonly ref: string\n}\n\ntype Listener = (entries: readonly FontCatalogEntry[]) => void\n\nexport class FontCatalog {\n private readonly builtin: readonly FontCatalogEntry[]\n private custom: readonly FontCatalogEntry[] = []\n private readonly listeners = new Set<Listener>()\n\n constructor(builtin: readonly FontCatalogEntry[] = []) {\n this.builtin = [...builtin]\n }\n\n /** 当前清单:内置在前(配置序),自定义追加在后(上传序) */\n get entries(): readonly FontCatalogEntry[] {\n return [...this.builtin, ...this.custom]\n }\n\n /** 追加自定义条目;同引用去重(重复上传不产生重复项,首见条目胜出) */\n addCustom(entry: FontCatalogEntry): boolean {\n if (this.entries.some((existing) => existing.ref === entry.ref)) return false\n this.custom = [...this.custom, entry]\n this.notify()\n return true\n }\n\n /** 订阅清单变更(整体快照推送,绑定层的响应式 ref 据此联动);返回退订函数 */\n subscribe(listener: Listener): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n private notify(): void {\n const entries = this.entries\n for (const listener of this.listeners) listener(entries)\n }\n}\n","/**\n * 上传注入点(工单 13):本机资源 → 可物化引用的宿主实现契约。\n *\n * core 只定义接口与消费动作,**不内置任何上传实现**(红线:图层 setter 禁 I/O\n * 的延伸——文档只持有引用字符串,本机字节到引用的搬运归宿主):生产宿主接自己\n * 的对象存储/上传服务返回 URL;playground 以 data URL 兜底(文件不经网络,可离\n * 线演示,代价是引用随 graph JSON 一起变大——演示语义,不作生产形态)。\n *\n * 文件以 DOM 无关的最小形态描述(core 无 DOM lib):绑定层从宿主 File 对象读出\n * 名称/MIME/字节后传入,内核不接触 File/Blob 等宿主类型。\n */\n\n/** 本机资源文件的最小描述(DOM 无关) */\nexport interface UploadFile {\n /** 原始文件名(含扩展名;上传实现可据其推断存储键/展示名) */\n readonly name: string\n /** MIME 类型(如 image/png、font/ttf;未知时可为空串) */\n readonly mime: string\n /** 文件字节 */\n readonly bytes: Uint8Array\n}\n\n/** 本机资源 → 可物化引用(URL / data URL / 宿主存储地址,渲染端物化管线可装载的引用) */\nexport type UploadHandler = (file: UploadFile) => Promise<string>\n\n/**\n * 上传建层的摆位覆盖(kbd-nav 工单 05,拖文件入画布语义):缺省(两键皆缺)=\n * 编辑器缺省盒(createDefaultLayer 的 ImageLayer 形态 200×150 落 0,0)。\n * - `at`:图层盒左上角的场景坐标(缺省 top-left 锚不改,改写锚点偏移);\n * - `size`:盒尺寸 1:1 落值不缩放(拖放语义传解码出的图片自然尺寸,溢出画布\n * 属预期——Figma 同构)。\n */\nexport interface UploadImagePlacement {\n readonly at?: { x: number; y: number }\n readonly size?: { width: number; height: number }\n}\n\n/** 上传条目的展示名:去扩展名的文件名基名(空退化全名) */\nexport function uploadDisplayName(file: UploadFile): string {\n const base = file.name.replace(/\\.[^.]+$/, '')\n return base === '' ? file.name : base\n}\n\n/** 未注入上传实现时的统一错误(宿主侧降级提示的对位语义) */\nexport class UploadHandlerMissingError extends Error {\n constructor() {\n super('未注入上传实现(uploadHandler):宿主可在创建 EditorSession 时提供本机资源 → 可物化引用的实现')\n this.name = 'UploadHandlerMissingError'\n }\n}\n","/**\n * spec 字段路径读写原语(shared):沿图层内字段路径(如 ['shape','backgroundColor']、\n * 单段 ['fontSize'])下降读取/原位写入。updateSpec(属性面板权威写入口)与样式\n * 粘贴(editing/styleClipboard 的快照抓取与逐字段落地)共用同一套下降语义。\n *\n * 中间段悬空(null/非对象)即放弃:字段形态由上层(领域类型/注册表)把关,\n * 原语只做防御性下降。写入侧尾段原位赋值,immer draft 语义下生效。\n */\n\n/** 沿字段路径下降读取;目标不是对象或中间段悬空返回 undefined */\nexport function readSpecField(target: unknown, key: readonly string[]): unknown {\n let node: unknown = target\n for (let i = 0; i < key.length - 1; i += 1) {\n if (node === null || typeof node !== 'object') return undefined\n node = (node as Record<string, unknown>)[key[i]!]\n }\n if (node === null || typeof node !== 'object') return undefined\n return (node as Record<string, unknown>)[key[key.length - 1]!]\n}\n\n/**\n * 沿字段路径下降写入(中间段悬空即放弃;尾段原位赋值,immer draft 语义下生效)。\n * 返回是否真写入——调用方据此统计落地字段(悬空放弃不计)。\n */\nexport function writeSpecField(target: unknown, key: readonly string[], value: unknown): boolean {\n if (target === null || typeof target !== 'object') return false\n let node: unknown = target\n for (let i = 0; i < key.length - 1; i += 1) {\n const next: unknown = (node as Record<string, unknown>)[key[i]!]\n if (next === null || typeof next !== 'object') return false\n node = next\n }\n ;(node as Record<string, unknown>)[key[key.length - 1]!] = value\n return true\n}\n","/**\n * 样式剪贴板内核语义(canvas-web-style-paste 工单 01):样式集/适用面常量 +\n * 复制快照/粘贴落地的纯函数面。\n *\n * - 样式 = 三族字段子集(spec 决策 2):盒内对齐(align.horizontal/vertical)、\n * 形状皮肤(shape.backgroundColor/shape.border/shape.padding)、文本族\n * (font/fontSize/fontColor/autowrap/angle/shape.lineHeight,仅文本层适用;\n * lineHeight 虽住 shape 段但随文本族走跨型规则)。明确排除:位置/尺寸/锚点\n * (几何不是样式)、name/visible/priority、数据字段 text/src/value(内容走\n * 复制/粘贴本体)、expression、表格结构字段(template/rows/rowsPath)。\n * - 适用面清单(spec 决策 3)按 LayerType 由内核 editing 域自持——对齐\n * editor-vue fieldSchema 的分组事实但**不引它**(依赖方向红线):fieldSchema\n * 给行模板替身只暴露高/高自适应(尺寸族不在样式集),故其适用面为空。\n * - 快照 = {sourceType, values}:逐字段 JSON 深拷贝(与源层断开引用,后续编辑\n * 源层不影响);拷贝不进历史(剪贴板态不是文档态,session 槽同门)。\n * - 粘贴 = 目标类型适用面 ∩ 快照字段逐字段 verbatim 覆盖:null 即值(无填充/\n * 无边框是样式事实)、Border 整对象覆盖;源不适型字段静默跳过;全等字段\n * 跳写——结构等值的新引用 immer 仍记 patch,跳写保「零变化 = 空 patch =\n * 不进历史」真短路。autoHeight⟹height=0 断言与 canonicalizeTableSync 重断言\n * 在 session 事务内一次收口(updateSpec 同门),本模块只做字段读写与判定。\n */\n\nimport type { Draft } from 'immer'\n\nimport type { Layer, LayerType } from '@hankchen/canvas'\n\nimport { readSpecField, writeSpecField } from '../shared/specField'\n\n/** 盒内对齐族(对齐 fieldSchema 对齐组) */\nconst ALIGN_FAMILY: readonly (readonly string[])[] = [\n ['align', 'horizontal'],\n ['align', 'vertical'],\n]\n\n/** 形状皮肤族(对齐 fieldSchema 形状组的样式三键;宽高/auto 不属样式) */\nconst SKIN_FAMILY: readonly (readonly string[])[] = [\n ['shape', 'backgroundColor'],\n ['shape', 'border'],\n ['shape', 'padding'],\n]\n\n/** 文本族(fieldSchema 文本组 + TextLayer 专属 angle 与 shape.lineHeight) */\nconst TEXT_FAMILY: readonly (readonly string[])[] = [\n ['font'],\n ['fontSize'],\n ['fontColor'],\n ['autowrap'],\n ['angle'],\n ['shape', 'lineHeight'],\n]\n\n/**\n * 按 LayerType 的样式字段适用面(spec 决策 3):键为图层内字段路径。行模板替身\n * 适用面为空——面板只暴露高/高自适应(尺寸族不在样式集),无样式可复制可贴。\n */\nexport const STYLE_FIELD_KEYS_BY_TYPE: Readonly<Record<LayerType, readonly (readonly string[])[]>> = {\n TextLayer: [...ALIGN_FAMILY, ...SKIN_FAMILY, ...TEXT_FAMILY],\n ImageLayer: [...ALIGN_FAMILY, ...SKIN_FAMILY],\n QrCodeLayer: [...ALIGN_FAMILY, ...SKIN_FAMILY],\n TableLayer: [...ALIGN_FAMILY, ...SKIN_FAMILY],\n TableRowLayer: [...ALIGN_FAMILY, ...SKIN_FAMILY],\n TableCellLayer: [...ALIGN_FAMILY, ...SKIN_FAMILY],\n TableRowTemplate: [],\n}\n\n/** 样式快照(样式剪贴板条目):源类型 + 适用面逐字段值 */\nexport interface StyleSnapshot {\n readonly sourceType: LayerType\n /** 字段值:键 = 字段路径点串(如 'align.horizontal'),值 = JSON 深拷贝 */\n readonly values: Readonly<Record<string, unknown>>\n}\n\n/** 字段键 → 快照记录键(点串;字段段为固定领域名,无歧义) */\nconst fieldId = (key: readonly string[]): string => key.join('.')\n\n/** 领域类型是纯 JSON 形态(往返恒等契约),JSON 往返即深拷贝(cloneLayerSubtree 同门) */\nfunction deepClone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T\n}\n\n/** 结构等值(stringify 即指纹;undefined === undefined 为真——两侧缺字段视为全等) */\nfunction jsonEqual(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b)\n}\n\n/**\n * 按源类型适用面抓逐字段深拷贝快照;适用面为空(行模板替身)返回 null——\n * 无样式可复制,调用方空转(空快照不占槽,已拷贝的样式不被无样式源清掉)。\n */\nexport function captureStyleSnapshot(layer: Layer): StyleSnapshot | null {\n const fields = STYLE_FIELD_KEYS_BY_TYPE[layer.type]\n if (fields.length === 0) return null\n const values: Record<string, unknown> = {}\n for (const key of fields) {\n const value = readSpecField(layer, key)\n if (value !== undefined) values[fieldId(key)] = deepClone(value)\n }\n return { sourceType: layer.type, values }\n}\n\n/** 逐字段应用快照到图层 draft(目标适用面 ∩ 快照):verbatim 覆盖、null 即值、\n * Border 整对象;快照没有的字段(源不适型)静默跳过;全等字段跳写。返回实际\n * 写入的字段键(空数组 = 零变化,调用方经 store 空 patch 空转)。\n */\nexport function applyStyleFieldsInDraft(\n layer: Draft<Layer>,\n snapshot: StyleSnapshot,\n): (readonly string[])[] {\n const written: (readonly string[])[] = []\n for (const key of STYLE_FIELD_KEYS_BY_TYPE[layer.type]) {\n const value = snapshot.values[fieldId(key)]\n if (value === undefined) continue // 源不适型:快照无此字段\n const current = readSpecField(layer, key)\n if (jsonEqual(current, value)) continue // 全等跳写:保零变化真短路\n if (writeSpecField(layer, key, deepClone(value))) written.push(key)\n }\n return written\n}\n","/**\n * 滚轮意图分类(工单 05,三态)。纯函数:输入只取事件修饰键快照,\n * 由绑定层(CanvasSurface)把 WheelEvent 映射进来后在内核判定。\n *\n * 语义(spec 拍板):ctrl/cmd + wheel 与触控板双指捏合 = 缩放(浏览器把\n * pinch 一律派发为 ctrl+wheel)、plain wheel = 平移、shift + wheel = 横向平移。\n */\n\nexport type WheelIntent = 'zoom' | 'pan' | 'pan-x'\n\n/** WheelEvent 的语义字段快照(内核不持有 DOM 事件类型) */\nexport interface WheelInput {\n ctrlKey: boolean\n metaKey: boolean\n shiftKey: boolean\n}\n\nexport function classifyWheel(input: WheelInput): WheelIntent {\n // 缩放优先于 shift:捏合手势(ctrl+wheel)不因 shift 串台为横移\n if (input.ctrlKey || input.metaKey) return 'zoom'\n if (input.shiftKey) return 'pan-x'\n return 'pan'\n}\n","/**\n * 候选枚举器(content-completion 工单 02,spec §2 候选枚举规则表)。\n *\n * 规则逐条导出自 php-canvas-next 求值/填充语义(InterpolationEvaluator +\n * CanvasHydrator,只读对齐),非新发明:\n * - 根上下文(根层三字段)头部候选 = 载荷顶层键(D1:schema 声明即 data 载荷\n * 形状)+ `$root`;行上下文(格内容层)追加 `row` + `$index`。`$index` 仅行\n * 上下文给——行外求值为空串非报错,但不给写得出来而无效的表达式;`row.` 行外\n * 报 `expression_row_outside_loop`,同样不给。\n * - 裸键在两种上下文都走根 schema:行上下文求值上下文 = `$rowNamespace + $root`\n * 合并(CanvasHydrator::evaluateNode),行命名空间只有 row/$index 且\n * reserved_root_key 保证根级键不遮蔽——裸键即根键。\n * - `.` 下钻走 properties 树;无 properties 节点终止该分支;数组节点收敛为叶子\n * 无具名子候选(D9 修订 D3:具名段下钻数组在求值器是静默空串——缺字段信号,\n * 不给写得出来而渲染为空白的表达式;下钻层 items 链保留供 rowsPath/`row.*`\n * 机制,类型徽标仍显示 array 本身)。开放映射(open)节点的枚举结果携带 open\n * 信号(结果面扩展,不污染候选条目契约),供工单 10 浮层占位提示(D10)。\n *\n * 消费契约(工单 04 浮层):\n * - 入参 expr 是片段表达式(trim 后,来自 expressionScan);**单个尾点 = 补全点\n * 标记**,先剥再解析(对齐工单 01 备注:`row.` 按求值语义是空段错误,剥点后\n * `row` 才是可下钻前缀);剥点后为空且原 expr 非空 = 孤点前缀,无合法续写 →\n * expression_syntax_error 信号。\n * - 结果 `partial` 恒为 expr 的后缀(尾点态 = 空后缀):接受 = 在 expr 末尾把\n * partial 替换为候选 segment;候选给**全量合法子项**、不按 partial 预过滤,\n * 前缀过滤/高亮归浮层(Ctrl+Space 全量展示语义亦归浮层)。\n * - 元信息:path(补全后的完整点路径)+ schema description/title(D8 两字段分离\n * 透传,展示回落 description ?? title 归浮层)+ 类型徽标数据(type 归一)。\n * `$root`/`row`/`$index` 结构头是下钻踏脚石(`{{$root}}`/\n * `{{row}}` 单独求值无意义),元信息仅有行 schema 声明时的 row 携带。\n *\n * 降级语义:schema null(声明被拒/形态非法,经 normalizeExpressionSchemaSource\n * 收口)→ 全部无候选;schema 合法而行 schema 未解析(rowsPath 无 items 声明)→\n * 结构头 row 仍在、行子树无候选——辅助声明不作权威,全程不抛错。\n *\n * 全模块纯函数、无 DOM;层内依赖仅 expressionSchema / expressionPath。\n */\nimport { parseExpressionPath, type ExpressionPathHead, type ExpressionSyntaxError } from './expressionPath'\nimport {\n schemaChildEntries,\n schemaNodeAtPath,\n type ExpressionSchemaChildEntry,\n type ExpressionSchemaNode,\n} from './expressionSchema'\n\n/** 'root' = 根层三字段(独立图层);'row' = 格内容层(模板行内) */\nexport type ExpressionContextKind = 'root' | 'row'\n\n/** 一条补全候选:路径 + schema 元信息(description/title/类型徽标,展示回落归浮层) */\nexport interface ExpressionCandidate {\n /** 补全后的完整点路径(前缀 + 本段;头部候选即头部名),如 row.user.city */\n path: string\n /** 本段名——接受时替换 partial / 追加到前缀后的文本 */\n segment: string\n /** schema description(未声明为 undefined) */\n description?: string\n /** schema title(D8 形状树两字段分离透传,未声明为 undefined)——浮层展示取 description ?? title */\n title?: string\n /** 类型徽标数据(schema type 归一;未声明为 undefined) */\n type?: string\n}\n\nexport interface ExpressionCandidateQuery {\n context: ExpressionContextKind\n /** 当前片段表达式(trim 后;'' = 刚输入 {{;允许单个补全尾点) */\n expr: string\n /**\n * 行上下文的行键树 schema(rowsPath 数组 items,resolveRowSchema 产物)。\n * 嵌套模板表的行相对 rowsPath 由调用方按当前行 schema 解析后传入。\n * 未传/解析不出 = 行子树无候选,结构头 row 仍给。\n */\n rowSchema?: ExpressionSchemaNode | null\n}\n\nexport type ExpressionCandidateResult =\n | {\n ok: true\n /** 候选所属前缀路径('' = 上下文头部层) */\n prefix: string\n /** 正在输入的未完整段('' = 尾点/空表达式态);恒为 expr 的后缀 */\n partial: string\n candidates: readonly ExpressionCandidate[]\n /**\n * 开放映射信号(D10):候选来源节点(头部层 = 根 schema,下钻层 = 前缀\n * 节点)标 open 时为 true,非 open 恒缺省——供浮层渲染「动态字段,键由\n * 模板定义」占位提示(工单 10),不污染候选条目契约。\n */\n open?: boolean\n }\n | { ok: false; reason: ExpressionSyntaxError }\n\nfunction headName(head: ExpressionPathHead): string {\n if (head.kind === 'row') return 'row'\n if (head.kind === 'root') return '$root'\n return head.name\n}\n\nfunction candidateMeta(node: ExpressionSchemaNode): Pick<ExpressionCandidate, 'description' | 'title' | 'type'> {\n const meta: Pick<ExpressionCandidate, 'description' | 'title' | 'type'> = {}\n if (node.description !== undefined) meta.description = node.description\n if (node.title !== undefined) meta.title = node.title\n if (node.type !== undefined) meta.type = node.type\n return meta\n}\n\nfunction childCandidate(entry: ExpressionSchemaChildEntry, prefix: string): ExpressionCandidate {\n const path = prefix === '' ? entry.key : `${prefix}.${entry.key}`\n return { path, segment: entry.key, ...candidateMeta(entry.node) }\n}\n\n/** 上下文头部候选:裸键(声明序)+ $root;行上下文追加 row/$index(结构头,踏脚石);open 信号随根节点透出 */\nfunction headCandidates(\n schema: ExpressionSchemaNode,\n context: ExpressionContextKind,\n rowSchema: ExpressionSchemaNode | null | undefined,\n): { candidates: readonly ExpressionCandidate[]; open?: boolean } {\n const view = schemaChildEntries(schema)\n const candidates = view.entries.map((entry) => childCandidate(entry, ''))\n candidates.push({ path: '$root', segment: '$root' })\n if (context === 'row') {\n candidates.push(rowSchema ? { path: 'row', segment: 'row', ...candidateMeta(rowSchema) } : { path: 'row', segment: 'row' })\n candidates.push({ path: '$index', segment: '$index' })\n }\n return view.open === true ? { candidates, open: true } : { candidates }\n}\n\n/**\n * 枚举当前片段表达式的合法候选。schema null(声明降级态)直接无候选;\n * 其余按 spec §2 规则表分上下文路由(见模块头注),分支不可达/节点为叶子\n * 一律空候选,不抛错。\n */\nexport function enumerateExpressionCandidates(\n schema: ExpressionSchemaNode | null,\n query: ExpressionCandidateQuery,\n): ExpressionCandidateResult {\n if (schema === null) {\n return { ok: true, prefix: '', partial: '', candidates: [] }\n }\n\n // 单个尾点 = 补全点标记(工单 01 备注);孤点前缀无合法续写\n const hadTrailingDot = query.expr.endsWith('.')\n const stripped = hadTrailingDot ? query.expr.slice(0, -1) : query.expr\n if (stripped === '') {\n if (query.expr === '') {\n return { ok: true, prefix: '', partial: '', ...headCandidates(schema, query.context, query.rowSchema) }\n }\n return { ok: false, reason: 'expression_syntax_error' }\n }\n\n const parsed = parseExpressionPath(stripped)\n if (!parsed.ok) {\n return { ok: false, reason: 'expression_syntax_error' }\n }\n\n const { head, rest } = parsed\n // 前缀路径与正在输入的段:尾点态全路径即前缀;否则尾段是 partial、其余是前缀;\n // 余段为空 = 头部本身在输入中(前缀回落到上下文头部层)\n let prefixPath: readonly string[]\n let partial: string\n if (hadTrailingDot) {\n prefixPath = [headName(head), ...rest]\n partial = ''\n } else if (rest.length > 0) {\n prefixPath = [headName(head), ...rest.slice(0, -1)]\n partial = rest[rest.length - 1] as string\n } else {\n prefixPath = []\n partial = headName(head)\n }\n\n if (prefixPath.length === 0) {\n return { ok: true, prefix: '', partial, ...headCandidates(schema, query.context, query.rowSchema) }\n }\n\n // 头路由对齐求值器 resolveFragment 的 match 分支。base 与下钻段:\n // 裸键头是根 schema 的真实段(段含头名);$root/row 是虚拟头(base 已是其\n // 指向的节点,段剥头名)\n let base: ExpressionSchemaNode | null\n let segments: readonly string[]\n if (head.kind === 'row') {\n if (query.context !== 'row') {\n return { ok: true, prefix: prefixPath.join('.'), partial, candidates: [] } // row. 行外不给\n }\n base = query.rowSchema ?? null\n segments = prefixPath.slice(1)\n } else if (head.kind === 'root') {\n base = schema\n segments = prefixPath.slice(1)\n } else if (head.name === '$index') {\n // $index 仅行上下文给(头部层已按上下文过滤);标量注入值无子候选\n return { ok: true, prefix: prefixPath.join('.'), partial, candidates: [] }\n } else {\n base = schema // 裸键 = 根键(两种上下文一致,见模块头注)\n segments = prefixPath\n }\n\n const node = base === null ? null : schemaNodeAtPath(base, segments)\n const prefix = prefixPath.join('.')\n if (node === null) {\n return { ok: true, prefix, partial, candidates: [] }\n }\n const view = schemaChildEntries(node)\n return {\n ok: true,\n prefix,\n partial,\n candidates: view.entries.map((entry) => childCandidate(entry, prefix)),\n ...(view.open === true ? { open: true } : {}),\n }\n}\n","/**\n * rowsPath(取行路径)候选枚举器 + 起点三分流判别(rows-path-completion 工单 01,\n * spec §2 候选规则 + §4.1 落位 D9)。\n *\n * 候选规则逐条导出自水合语义(CanvasHydrator::hydrateTemplateNode,只读对齐):\n * rowsPath 点路径自起点 schema 逐段下钻,终点必须是行数组——\n * - object 子段 = 可继续下钻的中间站候选(无徽标数据);array 子段(type 声明为\n * array 或声明了 items)= 合法终点候选,type 徽标数据恒 'array'(「行数组」展示\n * 文案归后续组装面,浮层零改动);标量子段不出候选(怂恿无效路径即怂恿\n * rows_path_invalid)。\n * - 数组内不下钻:具名段穿数组在水合端无意义(navigateRows 对列表键不命中),\n * 完成段任一落点为数组/标量 → null——数组之下不再产出任何候选(「枚举为空」\n * 以走不通同款收口:无补全态、浮层关闭、手输不受阻不阻断),与声明漂移/缺失\n * 同款,全程不抛错。\n * - open 节点(additionalProperties: true):open 信号随结果透出(信号面与候选\n * 空否无关,对齐 schemaChildEntries 的 D10 面),供浮层渲染占位提示行。\n * - 候选给当前层全量合法子项、不按 partial 预过滤(前缀过滤归工单 02 浮层面,与\n * 表达式枚举器分工同款);单尾点 = 剥点列全量子段;partial 恒为输入串后缀。\n * - 元信息:path(起点相对完整点路径)+ description/title 两字段分离透传(展示\n * 回落 description ?? title 归浮层)+ type 徽标数据。\n *\n * 与 expressionCandidates(content-completion D9)的分叉点:那里数组节点收敛为\n * 叶子(具名段下钻数组在求值器是静默空串——缺字段信号),本题以数组为合法终点\n * ——语义相反,故自实现逐层枚举、**不复用 schemaChildEntries**;下钻组合现成件\n * schemaNodeAtPath + 节点判别(isArrayNode/isScalarNode)。\n *\n * 起点三分流判别(D3):按选中路径位置定枚举起点 schema——无 template 祖先\n * (根层表;建表表单同款恒根起点,不经此判别件)= 载荷根 schema;有 template\n * 祖先 = 沿路径每个 template 段行相对递归 resolveRowSchema(嵌套表自外层行取数,\n * hydrateTemplateNode 同门;判别思路对齐 editor-vue expressionContext 的\n * rowSchemaAlongPath,缺省面相反:表达式面无祖先给 null 表根上下文,本面无祖先\n * 给根 schema 表根起点,故独立落件不改 expressionContext);任一层解析不出\n * (rowsPath 无 items 声明/漂移/途经非表)→ null 降级无候选。\n *\n * 全模块纯函数、零 DOM;层内依赖仅 expressionSchema / expressionCandidates\n * (候选类型面)/ layerPath。\n */\nimport type { Canvas } from '@hankchen/canvas'\n\nimport type { ExpressionCandidate } from './expressionCandidates'\nimport {\n resolveRowSchema,\n schemaNodeAtPath,\n type ExpressionSchemaNode,\n} from './expressionSchema'\nimport { resolveLayer, type LayerPath } from './layerPath'\n\n/**\n * 一条 rowsPath 候选(面同 ExpressionCandidate → 结构兼容 editor-vue 的\n * CompletionItem,浮层零改动复用)。\n */\nexport type RowsPathCandidate = ExpressionCandidate\n\n/** 一次枚举结果(partial/candidates 契约同表达式面 + open 信号);null = 无补全态 */\nexport interface RowsPathCandidateResult {\n /** 正在输入的未完整段('' = 空输入/单尾点态);恒为输入串后缀 */\n partial: string\n /** 当前层全量合法子项(不按 partial 预过滤——前缀过滤归浮层) */\n candidates: readonly RowsPathCandidate[]\n /**\n * 开放映射信号(D10 同款):枚举落点标 open(additionalProperties: true)时为\n * true,非 open 恒缺省——浮层渲染「动态字段,键由模板定义」占位提示行,信号\n * 在场时候选空也不关浮层。\n */\n open?: boolean\n}\n\n/** 节点判别:type 声明 array 或声明 items 即数组节点 = rowsPath 合法终点 */\nfunction isArrayNode(node: ExpressionSchemaNode): boolean {\n return node.type === 'array' || node.items !== undefined\n}\n\n/** 节点判别:无结构声明也无开放映射 = 标量节点(水合端不可导航) */\nfunction isScalarNode(node: ExpressionSchemaNode): boolean {\n return !isArrayNode(node) && node.properties === undefined && node.open !== true\n}\n\n/** 一条候选的元信息组装:终点数组徽标数据恒 'array'(展示文案不在此层定) */\nfunction childCandidate(key: string, node: ExpressionSchemaNode, prefix: string): RowsPathCandidate {\n const candidate: RowsPathCandidate = {\n path: prefix === '' ? key : `${prefix}.${key}`,\n segment: key,\n }\n if (node.description !== undefined) candidate.description = node.description\n if (node.title !== undefined) candidate.title = node.title\n if (isArrayNode(node)) candidate.type = 'array'\n return candidate\n}\n\n/**\n * 一层的合法子项候选(声明序):object/open 子段 = 中间站(无徽标数据);array\n * 子段 = 终点(type 'array');标量子段不出候选。open 信号随落点透出(与候选\n * 空否无关)。\n */\nfunction levelCandidates(\n base: ExpressionSchemaNode,\n prefix: string,\n): { candidates: readonly RowsPathCandidate[]; open?: boolean } {\n const candidates: RowsPathCandidate[] = []\n if (base.properties !== undefined) {\n for (const [key, child] of base.properties) {\n if (isScalarNode(child)) continue\n candidates.push(childCandidate(key, child, prefix))\n }\n }\n return base.open === true ? { candidates, open: true } : { candidates }\n}\n\n/**\n * 枚举当前输入串的合法 rowsPath 候选。起点 schema null(判别件降级态)直接无\n * 候选;完成段任一落点为数组/标量/缺失 → null(无补全态,不抛错——手输不受\n * 阻);枚举落点为对象层 → 该层全量合法子项。\n */\nexport function enumerateRowsPathCandidates(\n startSchema: ExpressionSchemaNode | null,\n input: string,\n): RowsPathCandidateResult | null {\n if (startSchema === null) return null\n\n // 单尾点 = 补全点标记:剥点列全量子段(partial 空);其余尾段即 partial\n const segments = input.split('.')\n const partial = input.endsWith('.') ? '' : (segments[segments.length - 1] as string)\n const completed = segments.slice(0, -1)\n\n let base = startSchema\n for (const segment of completed) {\n if (isArrayNode(base)) return null // 数组内不下钻:具名段穿数组在水合端无意义\n const child = schemaNodeAtPath(base, [segment])\n if (child === null) return null // 声明漂移/缺失/标量落点:无补全态不抛错\n base = child\n }\n if (isArrayNode(base) || isScalarNode(base)) return null // 枚举落点非对象层(终点数组之下无下钻)\n\n const view = levelCandidates(base, completed.join('.'))\n return {\n partial,\n candidates: view.candidates,\n ...(view.open === true ? { open: true } : {}),\n }\n}\n\n/**\n * rowsPath 编辑起点的三分流判别(D3):无 template 祖先 → 载荷根 schema(根层\n * 表;建表表单恒根起点同款);有 template 祖先 → 沿选中路径每个 template 段行\n * 相对递归 resolveRowSchema(首个对根 schema、其后对当前行 schema),返回最终\n * 行 schema;任一层解析不出(rowsPath 无 items 声明/漂移/途经非 TableLayer)→\n * null 降级无候选。rootSchema null(声明未注入/被拒)→ null;doc/path 缺席 →\n * 根起点兜底(画布级判定兜底一致)。\n */\nexport function resolveRowsPathStartSchema(\n doc: Canvas | null,\n path: LayerPath | null,\n rootSchema: ExpressionSchemaNode | null,\n): ExpressionSchemaNode | null {\n if (rootSchema === null) return null\n if (doc === null || path === null) return rootSchema\n let current: ExpressionSchemaNode | null = null\n for (let i = 0; i < path.length; i += 1) {\n if (path[i] !== 'template') continue\n const table = resolveLayer(doc, path.slice(0, i) as LayerPath)\n if (table === null || table.type !== 'TableLayer') return null\n current = resolveRowSchema(current ?? rootSchema, table.rowsPath)\n if (current === null) return null\n }\n return current ?? rootSchema\n}\n","/**\n * 文本布局策略的 memo 包装(canvas-web-render-perf 工单 02):把注入缝的\n * TextLayoutPolicies(断行器 + 度量器工厂)包上引用级缓存——全画布重绘对未变图层\n * 反复求布局(textLines 断行 / layerWidth 自然宽逐段度量)是逐键开销链的大头,\n * immer 结构共享保证未变图层的 (text, width, font, fontSize) 键稳定命中,逐键只\n * 重算被编辑的那一层。\n *\n * 键域与缓存:\n * - 断行:font \\0 fontSize \\0 width \\0 text → 行数组(同键同引用返回);\n * - 度量:font \\0 fontSize \\u0000 text → 宽(measure 纯函数,键即输入)。\n * 两缓存均 LRU(命中重插刷新位次),容量有界(缺省 512 / 2048,可配)——被编辑层\n * 的历史键随容量上限自然淘汰,长会话不无界增长。\n *\n * 缺省分量回落 core 缺省实现并同样被 memo(断行 = createUax14LineBreaker()、度量 =\n * heuristicMeasurerFactory)——宿主只注入 measureText 工厂时(playground 先例)断行\n * 缓存照样生效。核心包(canvas)零改动:本模块纯包装注入缝,语义与未包装完全\n * 一致(wire 零变化,Go/PHP 不跟进)。\n *\n * 上下文传递:断行器签名 (text, width, measurer) 不见 font/fontSize——本包装的\n * wrappedFactory 求值时把 (font, fontSize) 记进闭包上下文,wrappedBreaker 用它组键;\n * 布局消费恒为「先 factory 后 breaker」的同同步序列(layout.ts textLines/layerWidth),\n * 上下文读取因此总对应当前图层。\n */\nimport {\n createUax14LineBreaker,\n heuristicMeasurerFactory,\n type LineBreaker,\n type TextLayoutPolicies,\n type TextMeasurerFactory,\n} from '@hankchen/canvas'\n\nexport interface MemoTextPoliciesOptions {\n /** 断行缓存容量(键数),缺省 512 */\n readonly maxLineEntries?: number\n /** 度量缓存容量(键数),缺省 2048 */\n readonly maxMeasureEntries?: number\n}\n\nconst DEFAULT_LINE_ENTRIES = 512\nconst DEFAULT_MEASURE_ENTRIES = 2048\n\n/** LRU 缓存:命中重插刷新位次,超容逐最旧(Map 插入序即位次) */\nclass LruCache<V> {\n private readonly entries = new Map<string, V>()\n constructor(private readonly capacity: number) {}\n\n get(key: string): V | undefined {\n const hit = this.entries.get(key)\n if (hit !== undefined) {\n this.entries.delete(key)\n this.entries.set(key, hit)\n }\n return hit\n }\n\n put(key: string, value: V): void {\n if (this.entries.size >= this.capacity) {\n const oldest = this.entries.keys().next().value\n if (oldest !== undefined) this.entries.delete(oldest)\n }\n this.entries.set(key, value)\n }\n}\n\nexport function memoizeTextPolicies(\n policies: TextLayoutPolicies = {},\n options: MemoTextPoliciesOptions = {},\n): TextLayoutPolicies {\n const linesCache = new LruCache<string[]>(options.maxLineEntries ?? DEFAULT_LINE_ENTRIES)\n const measureCache = new LruCache<number>(options.maxMeasureEntries ?? DEFAULT_MEASURE_ENTRIES)\n // 当前 (font, fontSize) 上下文:wrappedFactory 求值时刷新,wrappedBreaker 组键消费\n let context = { font: '', fontSize: 0 }\n\n const measureMemoFactory: TextMeasurerFactory = (font, fontSize) => {\n context = { font, fontSize }\n const base = (policies.measurerFactory ?? heuristicMeasurerFactory)(font, fontSize)\n return {\n measure(text: string): number {\n const key = `${font}\\u0000${fontSize}\\u0000${text}`\n const hit = measureCache.get(key)\n if (hit !== undefined) return hit\n const width = base.measure(text)\n measureCache.put(key, width)\n return width\n },\n }\n }\n\n const lineBreakerMemo: LineBreaker = (text, width, measurer) => {\n const key = `${context.font}\\u0000${context.fontSize}\\u0000${width}\\u0000${text}`\n const hit = linesCache.get(key)\n if (hit !== undefined) return hit\n const lines = (policies.lineBreaker ?? createUax14LineBreaker())(text, width, measurer)\n linesCache.put(key, lines)\n return lines\n }\n\n return { lineBreaker: lineBreakerMemo, measurerFactory: measureMemoFactory }\n}\n","/**\n * 资源物化状态的内核切片形态与图片层占位态判定(placeholder-padding-hint 工单 02)。\n *\n * 物化状态住内核 store 的 ui 分支(红线 3 的编辑器延伸:物化状态永不写 graph),\n * 由宿主从渲染端物化状态机的散列键切片桥接注入——整体替换、内容等短路。键取\n * 图片 src 原串:键反解(散列键 → src)在宿主桥完成,内核零散列依赖。\n *\n * 占位态判定是「padding 调了看不到像素变化」的内核权威面:图片层内容绘制发生在\n * drawImage 分支,凡 drawImage 被跳过的层 padding 与像素无关(排查记录\n * canvas-web/layer-padding-research.md 根因 3)。四种未绘制形态:\n * ① 表达式标记图片——设计态预览把 src 置 null(preview.ts 占位盒语义),编辑器最常见;\n * ② 静态空引用/物化未达 done——render 源引用 null 只画盒、后端查不到已物化位图\n * 只画盒(pending/failed 同族);无记录 = 不可知,不假报(宿主未桥接不误报);\n * ③ 声明尺寸为零的自适应图片(解码清零)与极端 padding——内容盒 ≤ 0,后端\n * drawImage ≤0 防护恒跳过。判定取内容盒条件本身(忠实渲染面),auto 标志只是\n * 清零的常见来路(编辑器内打开 auto 不清零声明值,按标志判会误报)。\n *\n * 纯函数、零 DOM;QR/文本层占位不在本判定面(工单范围是图片层)。\n */\nimport { contentHeight, contentWidth } from '@hankchen/canvas'\nimport type { Layer } from '@hankchen/canvas'\n\n/** 单条资源的物化态(与渲染端物化状态机的三态同构,宿主桥按值透传) */\nexport type ResourceStatus = 'pending' | 'done' | 'failed'\n\n/**\n * 资源物化状态切片(store ui 分支 `resourceStatuses` 的形态):key = 图片 src\n * 原串,value = 物化态。整体替换语义,只住会话态——不进历史、不写 graph、\n * wire 零键;openDocument 换文档重置(资源态描述当次文档)。\n */\nexport type ResourceStatusMap = Readonly<Record<string, ResourceStatus>>\n\n/**\n * 图片层设计态内容未绘制(占位态)判定:命中即「当前层内容未绘制,padding 不\n * 影响预览」。非图片层恒 false;statuses 缺省/无记录时物化维度不参与(不可知\n * 不假报——headless 用法与未桥接宿主的静态可加载层不误报)。\n */\nexport function isImageContentUndrawn(layer: Layer, statuses?: ResourceStatusMap): boolean {\n if (layer.type !== 'ImageLayer') return false\n // ① 标记态文档 src 恒镜像表达式原文(非可物化引用),预览视图置空走占位盒\n if (layer.expression !== null) return true\n // ② 静态空引用:render 源引用 null 只画盒(未物化/未加载占位语义同族)\n if (layer.src === null) return true\n // ③ 内容盒 ≤ 0:后端 drawImage 的 ≤0 防护恒跳过(自适应解码清零/极端 padding)\n if (contentWidth(layer) <= 0 || contentHeight(layer) <= 0) return true\n // ② 物化维度:宿主桥接切片里该 src 未达 done(pending/failed = 内容未绘制)\n if (statuses !== undefined) {\n const status = statuses[layer.src]\n if (status !== undefined && status !== 'done') return true\n }\n return false\n}\n","/**\n * @hankchen/canvas-editor headless 内核公共出口。\n *\n * src 按 session(会话门面/store/快捷键)→ spatial(视口几何/拾取/滚轮)→\n * editing(剪贴板/图层/表格/字体/上传)→ shared(layerPath 寻址原语)分层,\n * 下层禁止反向引用上层(dependency-cruiser 域隔离规则 editor-*-isolation 锁定)。\n * 内核只保留根 barrel 单出口:EditorSession 门面是宿主唯一 API,域目录是\n * 内部组织,不开子路径导出,避免宿主绕过门面拼装内核。\n * 红线:内核不依赖任何 UI 绑定层(Vue/React),无 DOM lib,测试全部在\n * Node 无 DOM 环境运行。\n */\nexport const PACKAGE_NAME = '@hankchen/canvas-editor' as const\n\nexport * from './session'\nexport * from './spatial'\nexport * from './editing'\nexport * from './shared'\n// 领域常量与类型的公共再出口:绑定层(editor-vue)按红线不得直连 canvas,\n// 字段描述注册表等消费面从这里取(工单 09 起)\nexport {\n ANCHORS,\n HORIZONTAL_ALIGNS,\n LAYER_TYPES,\n VERTICAL_ALIGNS,\n type Anchor,\n type Border,\n type BorderSide,\n type HorizontalAlign,\n type LayerType,\n type Padding,\n type VerticalAlign,\n} from '@hankchen/canvas'\nexport type {\n Canvas,\n Layer,\n LayerBox,\n TableLayer,\n TableCellLayer,\n TableRowLayer,\n TextLayer,\n} from '@hankchen/canvas'\n// 纯函数的公共再出口:文本编辑 overlay 的字体族解析(工单 11)——与 loadCanvasFont\n// 同一派生,编辑中 textarea 与内容层 canvas 呈现同一字体;绑定层经内核取用\nexport { canvasFontCssFamily } from '@hankchen/canvas-browser-renderer'\n"],"mappings":"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","names":["lineHeightPx","resolveChildAt","resolveLayerBox","child","resolveChildAt","resolveLayerBox","resolveLayerBox","anchorOffset","layerHeight","index","layerHeight","anchorOffset","lineHeightPx","childCandidate"]}
|
|
1
|
+
{"version":3,"sources":["../src/session/editor.ts","../src/spatial/camera.ts","../src/spatial/hitTest.ts","../src/shared/layerPath.ts","../src/spatial/snap.ts","../src/spatial/create.ts","../src/spatial/resize.ts","../src/shared/expressionSchema.ts","../src/shared/expressionPath.ts","../src/shared/expressionCall.ts","../src/shared/expressionScan.ts","../src/editing/alignCanvas.ts","../src/editing/clipboard.ts","../src/editing/findReplace.ts","../src/editing/layerPanel.ts","../src/editing/tableEditing.ts","../src/session/store.ts","../src/session/shortcuts.ts","../src/session/editorState.ts","../src/editing/fontCatalog.ts","../src/editing/upload.ts","../src/shared/specField.ts","../src/editing/styleClipboard.ts","../src/spatial/wheel.ts","../src/shared/expressionFunctions.ts","../src/shared/expressionCandidates.ts","../src/shared/rowsPathCandidates.ts","../src/shared/textPoliciesMemo.ts","../src/shared/resourceStatus.ts","../src/index.ts"],"sourcesContent":["/**\n * EditorSession:headless 编辑会话——store 之上的渲染调度面(工单 05)。\n *\n * 职责(impl 研究文档 §2.1 的「双 canvas + 单 rAF 合帧 + 脏标」):\n * - store 订阅 → 分层脏标(doc/视口 → 双层;后续工单的选择/gizmo 只脏覆盖层)\n * → 注入的帧调度器合帧,每屏帧至多一次重绘;rAF 由绑定层注入,内核保持无 DOM。\n * - 内容层重绘驱动五原语模板(renderCanvas),视口变换喂给后端的可选能力口;\n * - 覆盖层(gizmo)绘制经 OverlayPainter 缝交给宿主:入参是与内容层一致的\n * 呈现视口(已对齐物理像素),由宿主自行施加变换后绘制;本层只保证\n * 「覆盖层脏不触发内容层」与两侧视口的一致性。\n * - 相机动作全部经 camera.ts 纯函数落到 store.ui.viewport(不进历史)。\n */\nimport {\n docPathToViewPath,\n lineHeightPx,\n renderCanvas,\n textOrigin,\n viewPathToDocPath,\n withTemplatePreview,\n type Canvas,\n type HorizontalAlign,\n type Padding,\n type RenderBackend,\n type TextLayoutPolicies,\n type TextLayer,\n type VerticalAlign,\n} from '@hankchen/canvas'\nimport type { PreviewViewportTransform, ViewportAwareBackend } from '@hankchen/canvas-browser-renderer'\nimport type { Draft } from 'immer'\nimport {\n DEFAULT_ZOOM_BOUNDS,\n PAN_TO_BOX_MARGIN_PX,\n fitRect,\n fitViewport,\n nextZoomByWheel,\n panBy,\n panToBox as panToBoxViewport,\n screenToScene,\n snapViewportToPhysicalPixels,\n zoomAtPoint,\n type Point,\n type Rect,\n type Size,\n type Viewport,\n type ZoomBounds,\n} from '../spatial/camera'\nimport type { LayerBox, Layer, LayerType, ImageLayer } from '@hankchen/canvas'\nimport { hitTest as hitTestAt } from '../spatial/hitTest'\nimport {\n resolveSnap,\n resolveSnapPoints,\n snapAxesFromBoxes,\n snapThresholdScene,\n visibleRootBoxes,\n type Guide,\n type GuideOrientation,\n type SnapAxis,\n} from '../spatial/snap'\nimport { CREATE_DEAD_ZONE_SCREEN_PX, createRubberBandRect } from '../spatial/create'\nimport {\n handleResizesHeight,\n handleResizesWidth,\n resizeBox,\n resizeHandleAt as resizeHandleHit,\n resizeHandlesAt as resizeHandlesForPath,\n resizeSnapPoints,\n resizableAxesAt,\n RESIZE_HANDLES,\n type ResizeHandle,\n} from '../spatial/resize'\nimport { anchorOffset, layerHeight, layerWidth } from '@hankchen/canvas'\nimport {\n isTemplateSubtreePath,\n isLockedPath,\n layerBoxByPath,\n isRootLayerPath,\n pathStartsWith,\n pathsEqual,\n remapPathAfterSplice,\n resolveLayer,\n rootLayerOf,\n selectionParentPath,\n type LayerPath,\n} from '../shared/layerPath'\nimport { normalizeExpressionSchemaSource } from '../shared/expressionSchema'\nimport { scanExpressionFragments } from '../shared/expressionScan'\nimport {\n ALIGN_CORNER_MARGIN_PX,\n alignToCanvasTarget,\n type AlignToCanvasMode,\n type AlignToCanvasOptions,\n} from '../editing/alignCanvas'\nimport {\n PASTE_OFFSET_PX,\n canCopyLayerAt as canCopyLayerAtPath,\n cloneLayerSubtree,\n pastePosition,\n prepareRootPaste,\n type ClipboardEntry,\n} from '../editing/clipboard'\nimport {\n replaceHitsInDraft,\n scanTextMatches,\n type FindTextHit,\n} from '../editing/findReplace'\nimport {\n addRootLayerInDraft,\n createDefaultLayer,\n createTemplateTable,\n deleteLayerInDraft,\n insertRootLayerAdjacentInDraft,\n insertRootLayerInDraft,\n moveRootLayerInDraft,\n moveTableRowInDraft,\n pickNextTextStylePatch,\n TEXT_PRESETS,\n type DeletedLayerRef,\n type NextTextStyle,\n type TextPresetId,\n} from '../editing/layerPanel'\nimport {\n addTableCellInDraft,\n addTableRowInDraft,\n addTemplateCellInDraft,\n canonicalizeTableSyncInDraft,\n convertTableToRowsInDraft,\n convertTableToTemplateInDraft,\n moveTableCellInDraft,\n moveTableCellToRowInDraft,\n moveTableRowToTableInDraft,\n setCellAutoHeightInDraft,\n type MovedSubtreeRef,\n} from '../editing/tableEditing'\nimport { EditorStore, type DragGesture, type EditorChange, type TransactOptions } from './store'\nimport { ARM_CREATE_LAYER_TYPES, type EditorShortcutAction } from './shortcuts'\nimport {\n parseCanvasEditorState,\n type CanvasEditorState,\n type EditorPrefsState,\n} from './editorState'\nimport { FontCatalog, type FontCatalogEntry } from '../editing/fontCatalog'\nimport {\n UploadHandlerMissingError,\n uploadDisplayName,\n type UploadFile,\n type UploadHandler,\n type UploadImagePlacement,\n} from '../editing/upload'\nimport {\n STYLE_FIELD_KEYS_BY_TYPE,\n applyStyleFieldsInDraft,\n captureStyleSnapshot,\n type StyleSnapshot,\n} from '../editing/styleClipboard'\nimport { writeSpecField } from '../shared/specField'\n\n/** transact 回调向外传值的容器:TS 会把闭包内赋值的 let 窄化回初值类型,盒属性访问不受影响 */\ntype TxOut<T> = { v: T }\n\n/** 帧调度器:返回取消函数(绑定层注入 requestAnimationFrame 的包装) */\nexport type FrameScheduler = (callback: () => void) => () => void\n\n/** 覆盖层绘制缝的入参:viewport 已对齐物理像素(与内容层同一呈现视口) */\nexport interface OverlayPaintArgs {\n viewport: Viewport\n dpr: number\n doc: Canvas | null\n}\n\nexport type OverlayPainter = (args: OverlayPaintArgs) => void\n\nexport type InvalidateTarget = 'content' | 'overlay' | 'both'\n\n/**\n * 文本编辑 overlay 的布局描述(textEditLayout 的产物):场景坐标系的盒几何与\n * 排版字段。CSS 换算(transform 缩放/字体族解析/行盒模型)归绑定层,内核保持\n * 无 CSS 语义。\n */\nexport interface TextEditLayout {\n /** 编辑中图层的绝对盒(场景坐标;textarea 覆盖整盒,文字按 padding 内缩) */\n box: LayerBox\n /** 进入编辑时的文档文本(textarea 初值) */\n text: string\n /** 原始字体引用(空串/纯数字 = 内置默认字体;URL 由绑定层解析注册族名) */\n font: string\n fontColor: string\n /** 场景字号(textarea 字号固定取它,缩放交给 CSS transform——字号视觉恒定) */\n fontSize: number\n /** 行高像素 = ceil(fontSize × lineHeight),与布局层同式 */\n lineHeightPx: number\n padding: Padding\n horizontalAlign: HorizontalAlign\n verticalAlign: VerticalAlign\n /**\n * 首行绘制锚点相对内容盒顶部的偏移(textOrigin.y,与 canvas 绘制同一数值)。\n * 锚点语义随 verticalAlign:top = 字形盒顶、center = 字形盒心、bottom = 字形盒底\n * (渲染端以字体 metrics 消化基线差,绑定层的 CSS 行盒换算须按同一语义折算,\n * 不能直接当 padding-top 用——否则 center/bottom 会差半行/一行)。\n */\n verticalAnchorY: number\n /** autowrap = textarea pre-wrap 软换行,否则 pre 单行(断行允许与预览不同,决策 A) */\n autowrap: boolean\n /**\n * 内容类型会话模式(canvas-web-expression-editing 工单 01):true = 表达式——\n * 绑定层 pill 激活态与补全 enabled 的消费面。取会话标志(编辑中 pill 切换\n * 实时跟随,不受层标记态影响——面板在会话窗口期显示的是提交前旧状态);路径\n * 无活动会话时按层标记态回落(进入编辑即锚定同值)。\n */\n expressionMode: boolean\n}\n\n/** 拖动事务的合并键:一次拖动的全部 pointermove 并成一步历史 */\nconst DRAG_MERGE_KEY = 'drag'\n\n/** 缩放事务的合并键(工单 07):一次八柄手势的全部位移并成一步历史 */\nconst RESIZE_MERGE_KEY = 'resize'\n\n/**\n * Alt+拖快速复制的死区(alt-drag-paste 工单 01,spec 决策 1):屏幕 css 像素、\n * 缩放无关(场景阈值 = 阈值 / zoom,SNAP_THRESHOLD_SCREEN_PX 同风格)——首移\n * 越过死区才插入副本,死区内抬手退化为普通点选。\n */\nexport const ALT_DRAG_DEAD_ZONE_SCREEN_PX = 4\n\n/** 微调步长(kbd-nav 工单 02):基础步 1 场景 px,Shift 大步 10 px 固定 */\nconst NUDGE_STEP_PX = 1\nconst NUDGE_COARSE_STEP_PX = 10\n\n/** 微调事务的合并键:含选中层路径(换层即换步)、方向无关(连按跨方向同串) */\nconst nudgeMergeKey = (path: LayerPath): string => `nudge:${path.join(':')}`\n\n/** 查找游标钳位:派生命中列表内回落(末处/首处),空命中为 0(读侧收口) */\nfunction clampFindCursor(cursor: number, count: number): number {\n if (count <= 0) return 0\n return Math.min(Math.max(cursor, 0), count - 1)\n}\n\n/**\n * 串含 ≥1 语法有效闭合片段(canvas-web-expression-editing 工单 01,spec 决策 3):\n * `scanExpressionFragments` 切出 `kind: 'fragment'` 且 `error === null`——与求值器\n * `expression_syntax_error` 同码。未闭合 `{{` 是 open 片段(求值期字面直通)、\n * 空片段/路径空段带错误信号,均不算合法片段——表达式会话提交去向(标记写 vs\n * 字面回落)的判定原语,守住不变量「标记 ⟹ ≥1 合法片段」。\n */\n/**\n * updateSpec 字段路径 → 新层样式槽字段(text-defaults 工单 01):文本族五键\n * 映射(shape.lineHeight 是唯一两段键),未命中返回 null——captureNextTextStyle\n * 的键面白名单。\n */\nfunction captureFieldOfKey(key: readonly string[]): keyof NextTextStyle | null {\n const single = key.length === 1 ? key[0] : null\n if (single === 'font' || single === 'fontSize' || single === 'fontColor' || single === 'autowrap') return single\n if (key.length === 2 && key[0] === 'shape' && key[1] === 'lineHeight') return 'lineHeight'\n return null\n}\n\nfunction hasValidExpressionFragment(template: string): boolean {\n return scanExpressionFragments(template).parts.some(\n (part) => part.kind === 'fragment' && part.error === null,\n )\n}\n\n/**\n * 解码不变量重断言(updateSpec 与样式粘贴共用,写入口收口一处):autoHeight\n * 置位即声明高归零(decodeBase 对 auto 标志恒清零声明高,PHP setHeight('auto')\n * 同门;文本层由布局按行数×行高+padding 动态求高,其余类型按各自兜底)——\n * 否则保存再打开声明高被清,往返恒等破。格的 autoHeight 写入另有采纳/固化\n * 语义(updateSpec 拦截分流),不会走到这里。参数收只读联合:draft 结构上\n * 兼容只读类型,写入经显式转型(字段形态由领域类型把关)。\n */\nfunction renormalizeAutoHeightInDraft(layer: Layer): void {\n if ((layer as { shape?: { autoHeight?: boolean } }).shape?.autoHeight === true) {\n ;(layer as { shape: { height: number } }).shape.height = 0\n }\n}\n\nexport interface EditorSessionOptions {\n scheduleFrame: FrameScheduler\n /** 缩放范围(可配置,缺省 5%–800%) */\n zoomBounds?: ZoomBounds\n /** 适应画布时的屏幕像素内缩(缺省 0:画布边贴视口边) */\n fitMargin?: number\n /** 文本布局策略(断行/度量注入缝,缺省启发式对齐版) */\n textPolicies?: TextLayoutPolicies\n /**\n * 上传实现注入点(工单 13):本机资源 → 可物化引用。core 不内置任何实现;\n * 未注入时 canUpload 为 false、上传动作抛 UploadHandlerMissingError(宿主\n * 据此做降级提示),生产宿主接自己的存储,playground 以 data URL 兜底。\n */\n uploadHandler?: UploadHandler\n /** 内置字体清单(URL 列表可配置;缺省空清单,清单 ≠ 物化,加载走渲染端管线) */\n fontCatalog?: readonly FontCatalogEntry[]\n}\n\nexport class EditorSession {\n readonly store: EditorStore\n\n private readonly scheduleFrame: FrameScheduler\n private readonly zoomBounds: ZoomBounds\n private readonly fitMargin: number\n /**\n * 生效的文本布局策略(构造注入值,缺省 undefined = 启发式对齐版):公开只读,\n * 供导出等旁路面复用同一注入值——导出与编辑画布的断行/盒高才不分叉(工单 13)。\n */\n readonly textPolicies?: TextLayoutPolicies\n private readonly uploadHandler: UploadHandler | null\n private readonly fontCatalogValue: FontCatalog\n\n private backend: RenderBackend | null = null\n private overlayPainter: OverlayPainter | null = null\n private dpr = 1\n private surfaceSize: Size = { width: 0, height: 0 }\n private dirty = { content: false, overlay: false }\n private frameQueued = false\n private cancelFrame: (() => void) | null = null\n private readonly unsubscribeStore: () => void\n /**\n * 预览视图 memo(决策 2026-09):模板态表格实例化一行预览行的文档衍生画布,\n * 画布渲染/命中/导出消费,永不写回 graph(红线 3 延伸)。Canvas 不可变 +\n * 结构共享,doc 引用相等即视图仍有效(与 skipContent 的引用比较同纪律)。\n */\n private previewSource: Canvas | null = null\n private previewValue: Canvas | null = null\n /**\n * 会话级剪贴板(工单 14;alt-drag-paste 工单 02 收窄):复制源的子树深拷贝\n * 快照 + 复制时点绝对盒(粘贴落位基准,格内容的格内相对 position 据此换算\n * 画布绝对落位)+ 源路径(粘贴时紧邻插入的解析凭据)。不跨会话、不碰 OS\n * 剪贴板;openDocument 不清空——粘贴进另一份文档是合法用法(条目是与源树\n * 断开引用的普通对象;他文档 sourcePath 必失配 → 粘贴退化置顶)。\n */\n private clipboardEntry: ClipboardEntry | null = null\n /**\n * 样式剪贴板(canvas-web-style-paste 工单 01):源类型 + 适用面逐字段值快照。\n * 与图层剪贴板**分立互不覆盖**(⌘C 与 ⌥⌘C 并存);不碰 OS 剪贴板、不进历史\n * (剪贴板态不是文档态);openDocument 不清空——跨文档粘贴样式合法(图层\n * 剪贴板同门,快照是与源树断开引用的普通对象)。\n */\n private styleClipboardEntry: StyleSnapshot | null = null\n /** 参考线 id 自增序(会话内唯一即可,不持久化——参考线不写 graph) */\n private guideIdSeq = 0\n\n constructor(options: EditorSessionOptions) {\n this.scheduleFrame = options.scheduleFrame\n this.zoomBounds = options.zoomBounds ?? DEFAULT_ZOOM_BOUNDS\n this.fitMargin = options.fitMargin ?? 0\n this.textPolicies = options.textPolicies\n this.uploadHandler = options.uploadHandler ?? null\n this.fontCatalogValue = new FontCatalog(options.fontCatalog)\n this.store = new EditorStore()\n this.unsubscribeStore = this.store.subscribe((change) => this.onStoreChange(change))\n }\n\n // ---- 组装与呈现参数(DOM 侧由绑定层调用) ----\n\n attachContentBackend(backend: RenderBackend): void {\n this.backend = backend\n this.invalidate('content')\n }\n\n setOverlayPainter(painter: OverlayPainter | null): void {\n this.overlayPainter = painter\n this.invalidate('overlay')\n }\n\n /** 解绑绘制面(surface 卸载时);挂起帧 flush 空转,可重新挂载 */\n detachSurfaces(): void {\n this.backend = null\n this.overlayPainter = null\n }\n\n setDevicePixelRatio(dpr: number): void {\n if (dpr === this.dpr) return\n this.dpr = dpr\n // 物理缓冲语义变更:内容与覆盖层缓冲都需重设后重绘\n this.invalidate('both')\n }\n\n setSurfaceSize(width: number, height: number): void {\n if (width === this.surfaceSize.width && height === this.surfaceSize.height) return\n this.surfaceSize = { width, height }\n this.invalidate('both')\n }\n\n getSurfaceSize(): Readonly<Size> {\n return this.surfaceSize\n }\n\n // ---- 相机动作(视口 {x, y, zoom} 走 ui 分支,不进历史) ----\n\n /**\n * 打开/替换文档(转发 store;解码在宿主侧完成,内核只消费领域结构)。\n * state 传切片即按 restoreEditorState 同一管线原子应用(reset → apply 切片 →\n * prune)——「开了就恢复」,宿主切帧处不可能忘记重放;切片形状不合法整体拒\n * (画布照常以默认态打开),现网单参调用零破坏。\n */\n openDocument(canvas: Canvas, state?: CanvasEditorState): void {\n this.store.openDocument(canvas)\n if (state === undefined) return\n this.restoreEditorState(state)\n }\n\n /**\n * 预览视图(只读衍生):模板态表格在视图中获得一行预览行(文本格 = 表达式\n * 原文、标记图片/二维码格走占位盒),渲染/命中/导出据此呈现「一行输出效果」;\n * 文档本身不被改写。宿主(如导出)与内核内部共用同一 memo。\n */\n get previewCanvas(): Canvas | null {\n const doc = this.store.doc\n if (doc === null) return null\n if (doc !== this.previewSource) {\n this.previewValue = withTemplatePreview(doc, this.textPolicies)\n this.previewSource = doc\n }\n return this.previewValue\n }\n\n /** 平移:屏幕位移按 zoom 折算 */\n panBy(deltaScreenX: number, deltaScreenY: number): void {\n this.store.setViewport(panBy(this.store.ui.viewport, deltaScreenX, deltaScreenY))\n }\n\n /** 缩放至指定倍率,以屏幕点(视口坐标系的 css 像素)为锚 */\n zoomAt(screenX: number, screenY: number, nextZoom: number): void {\n this.store.setViewport(zoomAtPoint(this.store.ui.viewport, screenX, screenY, nextZoom, this.zoomBounds))\n }\n\n /** 滚轮/捏合:曲线见 camera.nextZoomByWheel,缩放以指针为中心 */\n zoomByWheel(screenX: number, screenY: number, deltaY: number): void {\n const viewport = this.store.ui.viewport\n const nextZoom = nextZoomByWheel(viewport.zoom, deltaY, this.zoomBounds)\n this.store.setViewport(zoomAtPoint(viewport, screenX, screenY, nextZoom, this.zoomBounds))\n }\n\n /**\n * 缩放复位 100%(⌘0,kbd-nav 工单 01):以视口中心为锚回到 1.0——中心场景点\n * 不动、平移不跳变(Sketch Actual Size 同构,playground zoomTo100 先例收编为\n * 内核面);已在 100% 时 zoomAtPoint 恒等短路。视口走 ui 分支不进历史。\n */\n resetZoom(): void {\n const { width, height } = this.surfaceSize\n this.zoomAt(width / 2, height / 2, 1)\n }\n\n /** 一键适应画布:整页可见、居中(表面尺寸未知时 no-op) */\n fitToSurface(): void {\n const doc = this.store.doc\n if (!doc || this.surfaceSize.width <= 0 || this.surfaceSize.height <= 0) return\n this.store.setViewport(fitViewport(doc, this.surfaceSize, this.zoomBounds, this.fitMargin))\n }\n\n /**\n * 路径处图层盒:模板子树路径经预览视图解析(auto 高在视图里合成,文档侧\n * 声明高为零),其余路径直接解析文档——非模板子树结构共享,两棵树同盒。\n * gizmo/命中/适应选区/文本编辑 overlay 共用,与绘制不漂移。\n */\n private boxByPath(path: LayerPath): LayerBox | null {\n const doc = this.store.doc\n if (!doc) return null\n if (isTemplateSubtreePath(path)) {\n const view = this.previewCanvas\n const viewPath = view ? docPathToViewPath(doc, path) : null\n return view && viewPath ? layerBoxByPath(view, viewPath as LayerPath, this.textPolicies) : null\n }\n return layerBoxByPath(doc, path, this.textPolicies)\n }\n\n /** 自适应选区视图:视口适配选中盒;无选择/退化盒回落适应画布 */\n fitToSelection(): void {\n const doc = this.store.doc\n if (!doc) return\n const selection = this.store.ui.selection\n const box = selection ? this.boxByPath(selection) : null\n if (!box || box.width <= 0 || box.height <= 0 || this.surfaceSize.width <= 0 || this.surfaceSize.height <= 0) {\n this.fitToSurface()\n return\n }\n this.store.setViewport(fitRect(box, this.surfaceSize, this.zoomBounds, this.fitMargin))\n }\n\n /**\n * 保 zoom 平移入视(canvas-web-find-replace 工单 02,spec 决策 6):查找导航\n * 的视口跟随——目标盒完全在视口内视口不动,出视才最小平移使整盒入视(入视\n * 小边距与盒大于视口对齐盒左上的边界语义在 camera.panToBox 纯函数内定死)。\n * 与 fitToSelection 分立不混用:那是变焦语义(fitRect),这是保 zoom 的跟随\n * 语义。盒几何由调用方解析(工单 03 命中导航经 layerBoxAt 同一解析缝,模板\n * 子树走预览视图);视口走 ui 分支不进历史,视内不动经恒等短路零通知;表面\n * 尺寸未知 no-op(fitToSurface 同门)。\n */\n panToBox(box: Rect): void {\n if (this.surfaceSize.width <= 0 || this.surfaceSize.height <= 0) return\n const viewport = this.store.ui.viewport\n const next = panToBoxViewport(viewport, box, this.surfaceSize, PAN_TO_BOX_MARGIN_PX)\n if (next !== viewport) this.store.setViewport(next)\n }\n\n // ---- 选择与拖动(工单 06):命中/选择/拖动几何全部经内核纯函数 ----\n\n /** 屏幕(css 像素)→ 场景:绑定层的唯一坐标入口(zoom 折算收口在此) */\n toScenePoint(screenX: number, screenY: number): Point {\n return screenToScene(this.store.ui.viewport, screenX, screenY)\n }\n\n /**\n * 场景点命中查询(不产生副作用):在预览视图上执行——模板态表格的预览行\n * 可命中,命中路径经视图→文档映射落回模板子树身份(选中/属性编辑写文档)。\n * 锁定集合(canvas-web-layer-lock 工单 01)随 ui 分支传入纯函数:锁定的根层\n * 整子树退出命中面,selectAt/hoverAt/右键/双击全经此一条缝自动生效。\n */\n hitTest(sceneX: number, sceneY: number): LayerPath | null {\n const view = this.previewCanvas\n if (!view) return null\n const hit = hitTestAt(view, sceneX, sceneY, this.textPolicies, {\n lockedPaths: this.store.ui.lockedPaths,\n })\n if (hit === null) return null\n const docPath = viewPathToDocPath(view, hit)\n return docPath ? (docPath as LayerPath) : null\n }\n\n /** 路径处图层的绝对盒(gizmo 选择框与后续面板共用;与命中同一布局策略) */\n layerBoxAt(path: LayerPath): LayerBox | null {\n return this.boxByPath(path)\n }\n\n /** 点选:命中即选中并返回路径,未中清空选择(画布与后续面板同源) */\n selectAt(sceneX: number, sceneY: number): LayerPath | null {\n const path = this.hitTest(sceneX, sceneY)\n this.store.setSelection(path)\n return path\n }\n\n /** 直接设置选择(面板/键盘入口) */\n setSelection(path: LayerPath | null): void {\n this.store.setSelection(path)\n }\n\n /** Escape 升级:沿归属链 cell→row→table 逐级取父,链尽清空选择 */\n escapeSelection(): void {\n const selection = this.store.ui.selection\n if (!selection) return\n this.store.setSelection(selectionParentPath(selection))\n }\n\n /**\n * 循环选层(kbd-nav 工单 03,CONTEXT「循环选层」词条 / spec 决策 4):Tab\n * 沿面板序朝更垫底方向移动选中(面板顶 = 视觉最上层 = 数组尾,故数组下标 −1),\n * ⇧Tab 反向(selectPrevLayer),端点 wrap 回绕;空选从视觉最上层(面板首行)\n * 开始(spec 决策 4 字面口径,⇧Tab 同一起点);子层选中以根祖先为基准取相邻\n * 根层(不下钻表格,循环只落根路径)。跳过面与命中面同门(hitTest 同款判定):\n * 隐藏根层整子树排除(visible=false 是排除语义非保护语义)、锁定根层整子树\n * 退出交互面(isLocked 谓词——Tab 是命中面的键盘投影,「画布挡误触」同门,\n * layer-lock 合入后随谓词自动生效);全部根层被跳过时空转返回 false。\n * 选中变更走 setSelection(ui 分支零历史步;值等短路使单根层 wrap 自身无害\n * ——不通知)。返回是否落到选中(executeShortcut 可用态口径)。\n */\n selectNextLayer(): boolean {\n return this.cycleRootSelection(1)\n }\n\n /** ⇧Tab 反向循环选层(语义见 selectNextLayer) */\n selectPrevLayer(): boolean {\n return this.cycleRootSelection(-1)\n }\n\n /** 循环选层的共同游走:direction = 1 朝数组头(垫底)、−1 朝数组尾(最上),模 count 回绕 */\n private cycleRootSelection(direction: 1 | -1): boolean {\n const doc = this.store.doc\n const count = doc?.layers.length ?? 0\n if (doc === null || count === 0) return false\n // 基准根下标:任意深度的选中取根祖先(路径第 2 段即根下标);悬空选中\n // (undo prune 窗口外的瞬态)视同空选按起点口径\n const selection = this.store.ui.selection\n const baseline =\n selection !== null && rootLayerOf(doc, selection) !== null ? (selection[1] as number) : null\n const startIndex = baseline === null ? count - 1 : baseline - direction\n for (let step = 0; step < count; step += 1) {\n const index = (((startIndex - direction * step) % count) + count) % count\n const path: LayerPath = ['layers', index]\n // 跳过面与命中面同门:visible=false 整子树排除;锁定经 isLocked 谓词收口\n if (doc.layers[index]!.visible === false || this.isLocked(path)) continue\n this.store.setSelection(path)\n return true\n }\n return false\n }\n\n /** 悬停命中(指针移动时调用;离场传 null 或用 setHovered) */\n hoverAt(sceneX: number, sceneY: number): void {\n this.store.setHovered(this.hitTest(sceneX, sceneY))\n }\n\n /** 设置悬停路径 */\n setHovered(path: LayerPath | null): void {\n this.store.setHovered(path)\n }\n\n /**\n * 开始拖动会话:记录目标路径、起点场景坐标、起始 position 偏移与起始盒\n * (吸附求位基准,ui 分支)。已在拖动中、路径或盒几何无法解析时返回 false;\n * 路径落在锁定子树同样拒绝(canvas-web-layer-lock 工单 01:拖动是变更类\n * 动作的 guard 入口——画布命中面已过滤锁定层,此守卫兜住其余手势来源,\n * 缩放手势接入 resize feature 时同用此门)。\n *\n * Alt+拖快速复制(alt-drag-paste 工单 01):options.copy 起手一次性判定——\n * 源不可复制(canCopyLayerAt 为 false:行/格等容器内结构)静默忽略 Alt 走\n * 普通拖动(spec 决策 1);置位后 copyMode 会话内定死,首移越过死区才插入\n * 副本(dragTo 消费,见 ALT_DRAG_DEAD_ZONE_SCREEN_PX)。\n */\n beginDrag(path: LayerPath, sceneX: number, sceneY: number, options: { copy?: boolean } = {}): boolean {\n if (this.store.ui.drag || this.store.ui.resize || this.store.ui.create) return false\n if (this.isLocked(path)) return false\n const doc = this.store.doc\n const layer = doc ? resolveLayer(doc, path) : null\n if (!doc || !layer) return false\n const box = this.boxByPath(path)\n if (!box) return false\n const copy = options.copy === true && canCopyLayerAtPath(doc, path)\n this.store.setDrag({\n path,\n startScene: { x: sceneX, y: sceneY },\n startPosition: { x: layer.position.x, y: layer.position.y },\n startBox: box,\n ...(copy ? { copy: true, copyPending: true } : {}),\n })\n return true\n }\n\n /**\n * 拖动进行中:position = 起点 + 场景位移,再按吸附修正(工单 01,ADR 0012)。\n * 吸附求位:暂定盒(startBox + 本步位移)九点对轴集合——其他可见根层盒缘与\n * 中心、画布水平/垂直中轴、参考线轴——屏幕 6px 阈值按当前缩放换算场景值,\n * 多轴取最近,至多一垂直 + 一水平;修正并入同一位置事务(同一 mergeKey,\n * 跟随拖动既有历史合并语义)。命中的吸附轴回写会话态供吸附线呈现(瞬时\n * 回显,endDrag 清空)。九锚点一视同仁——锚点偏移不动,只累加 x/y(x += dx/\n * zoom 的 zoom 折算已由 toScenePoint 收口);一次拖动的全部位移经同 mergeKey\n * 事务并成一步历史,属性面板读同一文档数据即联动。\n *\n * Alt+拖 copyMode(alt-drag-paste 工单 01):副本未插入前(copyPending)源层\n * 不动——未越死区零事务,首移越阈交 dragCopyFirstMove 单事务完成插入 + 位移,\n * 此后 path 已重指向副本、走普通拖动流程(吸附排除副本、源层供轴)。\n */\n dragTo(sceneX: number, sceneY: number): void {\n const drag = this.store.ui.drag\n if (!drag) return\n // 拖动中图层被结构编辑移除的边缘态:整个求位空转(不发事务、不发布命中轴)\n const doc = this.store.doc\n if (!doc || resolveLayer(doc, drag.path) === null) return\n if (drag.copy === true && drag.copyPending === true) {\n this.dragCopyFirstMove(drag, sceneX, sceneY)\n return\n }\n const dx = sceneX - drag.startScene.x\n const dy = sceneY - drag.startScene.y\n const resolution = resolveSnap(\n {\n x: drag.startBox.x + dx,\n y: drag.startBox.y + dy,\n width: drag.startBox.width,\n height: drag.startBox.height,\n },\n this.snapAxesForGesture(drag.path),\n snapThresholdScene(this.store.ui.viewport.zoom),\n )\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, drag.path)\n if (!layer) return\n // immer draft 原位写 position;类型层的 readonly 由 draft 语义解除\n const position = layer.position as { x: number; y: number }\n position.x = drag.startPosition.x + dx + resolution.dx\n position.y = drag.startPosition.y + dy + resolution.dy\n }, { mergeKey: DRAG_MERGE_KEY })\n this.store.setSnapAxes(resolution.axes)\n }\n\n /**\n * copyMode 首移(alt-drag-paste 工单 01,spec 决策 1/3):先过死区——位移按\n * 屏幕 4px 阈值折算(缩放无关),未越阈零事务、源层不动(Alt+点击退化);\n * 越阈则单事务完成「插入副本(紧邻源层)+ 副本自源盒起步位移」:副本 = 源\n * 子树深拷贝,落位按根层语义反解(pastePosition——根层源退化为 position +\n * 位移、格内容源把格内相对 position 换算画布绝对落位),与吸附修正一并写入,\n * mergeKey 沿用 DRAG_MERGE_KEY,后续 move 同键合并——undo 一次副本整体消失。\n * 吸附求位在副本入库前:吸附源 = 全部可见根层(副本尚不存在、源层天然在列\n * ——重叠起步,spec 决策 4);插入后手势重指向副本(startPosition 换基准为\n * 副本起步位 = 源绝对盒的根层语义反解,startBox 即副本起步盒不变),副本自动\n * 选中,命中轴照常回显。\n */\n private dragCopyFirstMove(drag: DragGesture, sceneX: number, sceneY: number): void {\n const doc = this.store.doc\n if (!doc) return\n const source = resolveLayer(doc, drag.path)\n if (!source) return\n const dx = sceneX - drag.startScene.x\n const dy = sceneY - drag.startScene.y\n if (Math.hypot(dx, dy) <= ALT_DRAG_DEAD_ZONE_SCREEN_PX / this.store.ui.viewport.zoom) return\n const resolution = resolveSnap(\n {\n x: drag.startBox.x + dx,\n y: drag.startBox.y + dy,\n width: drag.startBox.width,\n height: drag.startBox.height,\n },\n this.snapAxesForGesture(null),\n snapThresholdScene(this.store.ui.viewport.zoom),\n )\n const copyIndex: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n const sourceInDraft = resolveLayer(draft, drag.path)\n if (!sourceInDraft) return\n const prepared = prepareRootPaste(\n sourceInDraft,\n drag.startBox,\n doc.width,\n doc.height,\n dx + resolution.dx,\n dy + resolution.dy,\n )\n copyIndex.v = insertRootLayerAdjacentInDraft(draft, prepared as Draft<Layer>, drag.path) ?? -1\n }, { mergeKey: DRAG_MERGE_KEY })\n if (copyIndex.v < 0) return\n const copyPath: LayerPath = ['layers', copyIndex.v]\n this.store.setDrag({\n ...drag,\n path: copyPath,\n startPosition: pastePosition(drag.startBox, source.position.anchor, doc.width, doc.height, 0, 0),\n copyPending: false,\n })\n this.store.setSelection(copyPath)\n this.store.setSnapAxes(resolution.axes)\n }\n\n /** 结束拖动:闭合合并事务(下一步历史定格),会话态与命中轴回显清空 */\n endDrag(): void {\n if (!this.store.ui.drag) return\n this.store.setDrag(null)\n this.store.setSnapAxes([])\n this.store.closeMerge(DRAG_MERGE_KEY)\n }\n\n /**\n * 手势可用的吸附轴集合(dragTo/resizeTo 每步重算,几何与其余消费同源):\n * 可见根层盒(hitTest 同款 visible 过滤)排除手势目标层自身根 + 画布中轴 +\n * 当前参考线。自身根排除对缩放同样正确——被缩放层的自缘不供轴(对缘与移动\n * 缘都在自身盒上,供轴只会命中自己)。gesturePath 传 null = 排除面为空\n * (copyMode 首移:副本未入库,源层供轴,alt-drag-paste 工单 01)。\n */\n private snapAxesForGesture(gesturePath: LayerPath | null): readonly SnapAxis[] {\n const doc = this.store.doc\n if (!doc) return []\n const excludeRootIndex = gesturePath === null ? null : gesturePath[1]\n if (excludeRootIndex !== null && typeof excludeRootIndex !== 'number') return []\n return snapAxesFromBoxes(\n doc.width,\n doc.height,\n visibleRootBoxes(doc, excludeRootIndex, this.textPolicies),\n this.store.ui.guides,\n )\n }\n\n /** 当次手势命中的吸附轴(只读查询,吸附线呈现消费);非手势态/未命中为空 */\n listSnapAxes(): readonly SnapAxis[] {\n return this.store.ui.snapAxes\n }\n\n // ---- 八柄缩放(工单 07):几何纯函数在 spatial/resize,写回/吸附/采纳在此收口 ----\n\n /**\n * 路径处图层当前可用的八柄集合(gizmo 柄渲染与手势起点同缝):角色权威\n * 过滤(行宽=表宽、格内容尺寸强同步面不可经柄改,语义见\n * spatial/resize.resizableAxesAt)+ 锁定子树不出柄(锁定层只画选中框,\n * gizmo 同门——柄是变换入口,锁定=可定位不可变换)。\n */\n resizeHandlesAt(path: LayerPath | null): readonly ResizeHandle[] {\n const doc = this.store.doc\n if (!doc || path === null || this.isLocked(path)) return []\n return resizeHandlesForPath(doc, path)\n }\n\n /**\n * 场景点命中选中层的可用柄(hitTest/hoverAt 同门的意图级查询,画布接线\n * 与 gizmo 消费):命中半径按屏幕 6px 常量折算(RESIZE_HANDLE_HIT_PX,随\n * zoom 收放);无选中/盒不可解析/未命中返回 null。命中判定以当前可用柄\n * 集合为面——角色过滤与锁定折叠在此一条缝生效。\n */\n resizeHandleAt(sceneX: number, sceneY: number): ResizeHandle | null {\n const selection = this.store.ui.selection\n if (selection === null) return null\n const box = this.boxByPath(selection)\n if (!box) return null\n return resizeHandleHit(\n this.resizeHandlesAt(selection),\n box,\n sceneX,\n sceneY,\n this.store.ui.viewport.zoom,\n )\n }\n\n /**\n * 开始缩放会话:记录目标路径、柄、起点场景坐标与起始解析盒(ui 分支)。\n * 起点记录指针位置而非柄中心——位移以抓取点为基准,移动缘不跳到指针下\n * (与拖动保留抓取偏移同语义,两种主流做法中取不跳变侧)。\n * 已在手势中(拖动/缩放互斥)、路径或盒几何无法解析、路径落在锁定子树、\n * 柄的任一作用轴不在角色可缩放面内(行上拖 e/w、格内容上任意柄)时返回\n * false——gizmo 只渲染可用柄,此守卫兜其余手势来源(键盘/宿主直调)。\n */\n beginResize(path: LayerPath, handle: ResizeHandle, sceneX: number, sceneY: number): boolean {\n if (this.store.ui.drag || this.store.ui.resize || this.store.ui.create) return false\n if (this.isLocked(path)) return false\n if (!RESIZE_HANDLES.includes(handle)) return false\n const doc = this.store.doc\n if (!doc || resolveLayer(doc, path) === null) return false\n const axes = resizableAxesAt(doc, path)\n if (handleResizesWidth(handle) && !axes.width) return false\n if (handleResizesHeight(handle) && !axes.height) return false\n const box = this.boxByPath(path)\n if (!box) return false\n this.store.setResize({ path, handle, startScene: { x: sceneX, y: sceneY }, startBox: box })\n return true\n }\n\n /**\n * 缩放进行中(工单 07):八柄几何求位 + 移动缘吸附 + 文档写回,一个 mergeKey\n * 事务合并成一步历史。\n *\n * 几何:移动缘 = 起始缘 + 场景位移,对缘固定,minSize 钳位不翻转\n * (resizeBox);非本柄轴的位移分量忽略。\n * 吸附:移动缘位点(resizeSnapPoints,角柄双缘、边柄单缘;盒中心不参与缩放\n * 吸附)对轴集合求位——轴集合与拖动同源(snapAxesForGesture),屏幕 6px 阈值\n * 随 zoom 折算;修正施加在指针位移上、再进钳位,吸附不破「对缘固定 + minSize」\n * 语义,吸附结果只改坐标不改尺寸语义(auto 标志采纳是手势起点的轴选择,不随\n * 吸附翻转)。命中的吸附轴回写会话态供吸附线呈现(瞬时回显,endResize 清空)。\n * 写回(工单验收项 2 的语义收口):被拖轴写声明尺寸并清 auto 标志——autoWidth/\n * autoHeight 图层首个位移事务把解析尺寸落地为声明值(「改尺寸标志」语义,\n * 属性面板 auto 前缀输入框的可编辑化同门);未被拖的轴零接触——autoWidth\n * 文本层拖 s 柄后仍随文本自然宽、QR 拖 e 柄高随宽保持正方形(从动轴不采纳)。\n * 写后重断言(updateSpec 同门):autoHeight⟹声明高归零的解码不变量 + 表格\n * 强同步 canonicalize(根表宽联动行宽、格内容宽同步、行高取最高格——行高缩到\n * 最高格以下被不变量顶回属预期)。position 按目标盒反解(锚点偏移随新尺寸\n * 重算,任意锚点/任意父级下落点都精确):同步完成后以草稿内的解析尺寸与\n * 父级盒求锚点偏移,position = 目标盒坐标 − 偏移 − 父级原点(嵌套路径的\n * position 是父级相对量,父级原点经 gestureParentOrigin 在写前状态反解——\n * 根层原点恒 (0,0),行为不变)。\n *\n * 拖不动写不动的同门守卫:无会话、目标层被结构编辑移除(悬空路径)整体\n * 空转(不发事务、不发布命中轴)。\n */\n resizeTo(sceneX: number, sceneY: number): void {\n const gesture = this.store.ui.resize\n if (!gesture) return\n const doc = this.store.doc\n const current = doc ? resolveLayer(doc, gesture.path) : null\n if (!doc || !current) return\n const dx = sceneX - gesture.startScene.x\n const dy = sceneY - gesture.startScene.y\n const widthActive = handleResizesWidth(gesture.handle)\n const heightActive = handleResizesHeight(gesture.handle)\n const resolution = resolveSnapPoints(\n resizeSnapPoints(gesture.startBox, gesture.handle, dx, dy),\n this.snapAxesForGesture(gesture.path),\n snapThresholdScene(this.store.ui.viewport.zoom),\n )\n const target = resizeBox(\n gesture.startBox,\n gesture.handle,\n widthActive ? dx + resolution.dx : dx,\n heightActive ? dy + resolution.dy : dy,\n )\n // 手势空间父级原点(嵌套路径 position 反解的坐标基准),事务前以求得——\n // 须用写前状态(盒/position/解析尺寸同源,见 gestureParentOrigin)\n const origin = this.gestureParentOrigin(gesture.path, current)\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, gesture.path)\n if (!layer) return\n // 被拖轴写声明尺寸并清 auto 标志(采纳);未被拖的轴零接触。\n // draft 语义解除 readonly;字段形态由领域类型与可缩放面把关\n const shape = layer.shape as {\n width: number\n height: number\n autoWidth: boolean\n autoHeight: boolean\n }\n if (widthActive) {\n shape.width = target.width\n shape.autoWidth = false\n }\n if (heightActive) {\n shape.height = target.height\n shape.autoHeight = false\n }\n // 解码不变量重断言 + 表格强同步(updateSpec 同门):先同步再反解\n // position——同步可能改写解析尺寸(格内容宽/行高)与父级盒(行高增长),\n // 反解必须以最终形态为准\n renormalizeAutoHeightInDraft(layer)\n canonicalizeTableSyncInDraft(\n draft,\n gesture.path,\n widthActive ? ['shape', 'width'] : ['shape', 'height'],\n this.textPolicies,\n )\n const resolvedWidth = layerWidth(layer, this.textPolicies)\n const resolvedHeight = layerHeight(layer, this.textPolicies)\n const parent = this.parentDimsInDraft(draft, gesture.path)\n const offset = anchorOffset(\n layer.position.anchor,\n parent.width,\n parent.height,\n resolvedWidth,\n resolvedHeight,\n )\n // draft 语义解除 readonly;position = 目标盒坐标 − 锚点偏移 − 父级原点\n // (任意锚点/任意父级下盒精确落位;父级原点见 gestureParentOrigin)\n const position = layer.position as { x: number; y: number }\n position.x = target.x - offset.x - origin.x\n position.y = target.y - offset.y - origin.y\n }, { mergeKey: RESIZE_MERGE_KEY })\n this.store.setSnapAxes(resolution.axes)\n }\n\n /**\n * 结束缩放:闭合合并事务(下一步历史定格),会话态与命中轴回显清空。\n * 无会话空转(幂等,pointercancel 与 pointerup 双路同达)。\n */\n endResize(): void {\n if (!this.store.ui.resize) return\n this.store.setResize(null)\n this.store.setSnapAxes([])\n this.store.closeMerge(RESIZE_MERGE_KEY)\n }\n\n // ---- 画拉建层(drag-create 工单 01):武装 + 橡皮筋纯视觉 + pointerup 落库 ----\n\n /**\n * 武装建层(面板新增项/层型快捷键的统一入口,CONTEXT「武装」词条):置 ui\n * 分支待命层型,下一次画布按下即开画拉;建层提交或 Esc 即解除——一次性\n * 待命态,无粘性工具(spec 决策 2)。重复武装同型值等短路零通知。\n *\n * preset(text-defaults 工单 01,CONTEXT「文本预设」词条):文本预设 = 写\n * 新层样式槽的快捷方式——命中档位先把档位值并入槽(v1 只 fontSize),再\n * 武装;仅 TextLayer 生效;未知 id = 跳过写槽、武装照常(评审口径统一:\n * 「空转」只指写槽,武装层型本身仍合法)。\n */\n armLayerCreate(type: LayerType, preset?: TextPresetId): void {\n if (preset !== undefined && type === 'TextLayer') {\n const entry = TEXT_PRESETS.find((candidate) => candidate.id === preset)\n if (entry) this.store.setNextTextStyle({ fontSize: entry.fontSize })\n }\n this.store.setArmedCreate(type)\n }\n\n /** 解除建层武装(取消入口):仅清待命态,零副作用。 */\n cancelArmLayerCreate(): void {\n this.store.setArmedCreate(null)\n }\n\n /**\n * 开始画拉会话:须已武装(未武装空转防御——画布手势态机的分派前置,此守卫\n * 兜其余调用来源),与拖动/缩放会话互斥(手势起点一次性定死,beginDrag/\n * beginResize 同门),文档未打开空转。层型在起点一次性捕获进会话(spec\n * 决策 7:拖拽中改主意 = Esc 重武装,v1 不做拖拽中层型切换)。会话只进\n * ui 分支——拖拽期纯视觉、文档零变更(spec 决策 4)。\n */\n beginLayerCreate(sceneX: number, sceneY: number): boolean {\n const armed = this.store.ui.armedCreate\n if (armed === null || this.store.ui.drag || this.store.ui.resize || this.store.ui.create) return false\n if (!this.store.doc) return false\n const point = { x: sceneX, y: sceneY }\n this.store.setCreate({\n type: armed,\n startScene: point,\n currentScene: point,\n rect: { x: sceneX, y: sceneY, width: 0, height: 0 },\n })\n return true\n }\n\n /**\n * 画拉进行中:橡皮筋求位(spatial/create.createRubberBandRect——两点正规化\n * + QR 钳 height := width + 左右/上下缘各两点进 resolveSnapPoints,吸附数学\n * 零新增)写回会话 rect 供 overlay 直读(虚线矩形 + W×H 气泡),命中轴回写\n * ui.snapAxes 走 GuidesOverlay 既有通道回显(dragTo 同门)。吸附排除面为空\n * (snapAxesForGesture(null)——新层尚未入库,全部可见根层供轴,copyMode\n * 首移同门)。零文档事务:修正只进会话几何,落库在 endCreate。\n */\n createTo(sceneX: number, sceneY: number): void {\n const gesture = this.store.ui.create\n const doc = this.store.doc\n if (!gesture || !doc) return\n const resolution = createRubberBandRect(\n gesture.type,\n gesture.startScene,\n { x: sceneX, y: sceneY },\n this.snapAxesForGesture(null),\n snapThresholdScene(this.store.ui.viewport.zoom),\n )\n this.store.setCreate({\n ...gesture,\n currentScene: { x: sceneX, y: sceneY },\n rect: resolution.rect,\n })\n this.store.setSnapAxes(resolution.axes)\n }\n\n /**\n * 结束画拉:按死区分流落库(一次手势 = 一步历史,undo 整体回退落位 + 尺寸\n * + priority)。先清会话再落文档(commitTextEdit 同门——退出路径级联时幂等):\n * - 死区外:单事务写入「createDefaultLayer 缺省形态 + position 左上角对准\n * 橡皮筋矩形(含吸附修正)+ shape.width/height」并 insertRootLayerInDraft\n * 置顶(priority min−1、push 数组尾);\n * - 死区内(点击兜底,位移按 CREATE_DEAD_ZONE_SCREEN_PX 屏幕 px 随 zoom 折算,\n * ALT_DRAG_DEAD_ZONE_SCREEN_PX 同门):缺省尺寸、左上角对准点击点。\n * 落库后自动选中新层并解除武装;返回新层路径(绑定层消费:文本层据此自动\n * 进文本编辑,spec 决策 6)。无会话/无文档空转返回 null。点击兜底不产生\n * 零尺寸层(缺省形态自带可见尺寸)。\n */\n endCreate(): LayerPath | null {\n const gesture = this.store.ui.create\n if (!gesture) return null\n this.store.setCreate(null)\n this.store.setSnapAxes([])\n this.store.setArmedCreate(null)\n if (!this.store.doc) return null\n const dx = gesture.currentScene.x - gesture.startScene.x\n const dy = gesture.currentScene.y - gesture.startScene.y\n const deadZoneScene = CREATE_DEAD_ZONE_SCREEN_PX / this.store.ui.viewport.zoom\n const clicked = Math.hypot(dx, dy) <= deadZoneScene\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n // 新层样式槽覆盖工厂缺省(text-defaults 工单 01):工厂仅 TextLayer 臂消费\n const layer = createDefaultLayer(gesture.type, this.store.ui.nextTextStyle) as Draft<Layer>\n if (clicked) {\n // 点击兜底:缺省尺寸原样,position 左上角对准点击点(拖文件入画布\n // 「左上角对准释放点」同门,spec 决策 5)\n layer.position = { ...layer.position, x: gesture.startScene.x, y: gesture.startScene.y }\n } else {\n layer.position = { ...layer.position, x: gesture.rect.x, y: gesture.rect.y }\n layer.shape.width = gesture.rect.width\n layer.shape.height = gesture.rect.height\n }\n index.v = insertRootLayerInDraft(draft, layer)\n })\n if (index.v < 0) return null\n const path: LayerPath = ['layers', index.v]\n this.store.setSelection(path)\n return path\n }\n\n /**\n * 取消画拉(Esc 语义):清橡皮筋会话、命中轴回显与武装态——零文档写入、\n * 零历史步(拖拽期纯视觉因此取消零回退成本,spec 决策 4)。幂等:无会话时\n * 仅解除武装(待命态 Esc 同走此口)。\n */\n cancelCreate(): void {\n this.store.setCreate(null)\n this.store.setSnapAxes([])\n this.store.setArmedCreate(null)\n }\n\n /**\n * 手势空间父级原点(嵌套路径 position 反解的坐标基准,resizeTo 专用):布局\n * 不变量 box = 父级原点 + 锚点偏移 + position(render.resolveLayerBox 同式)\n * 对被拖层成立,且父级原点在自身缩放中不动——兄弟/祖先几何不被自身尺寸写回\n * 牵动(格宽累加/行高累加只受前序兄弟影响)。故以手势同空间(模板子树经预览\n * 视图解析,与 startBox/startScene 同一坐标系)的当前盒线性反解:原点 = 当前\n * 盒 − 锚点偏移(手势空间父级盒尺寸入参)− 当前 position。根层恒 (0,0)——\n * 既有根层语义(父级 = 画布、原点即画布原点)不变;盒不可解析回落实地 (0,0)\n * (与根层同参,写回仍自洽)。须以写前状态调用:盒/position/解析尺寸三者\n * 同源才能精确反解。\n */\n private gestureParentOrigin(path: LayerPath, layer: Layer): { x: number; y: number } {\n if (isRootLayerPath(path)) return { x: 0, y: 0 }\n const doc = this.store.doc\n if (!doc) return { x: 0, y: 0 }\n const box = this.boxByPath(path)\n if (!box) return { x: 0, y: 0 }\n const parentPath =\n path[path.length - 1] === 'content' ? path.slice(0, -1) : path.slice(0, -2)\n const parentBox = this.boxByPath(parentPath as LayerPath)\n const offset = anchorOffset(\n layer.position.anchor,\n parentBox?.width ?? doc.width,\n parentBox?.height ?? doc.height,\n layerWidth(layer, this.textPolicies),\n layerHeight(layer, this.textPolicies),\n )\n return {\n x: box.x - offset.x - layer.position.x,\n y: box.y - offset.y - layer.position.y,\n }\n }\n\n /**\n * 草稿内求缩放目标的父级盒尺寸(position 反解的锚点偏移入参):根层 = 画布\n * 尺寸(resolveLayerBox 根层语义同参);行/格取容器路径(rows/cells 段成对、\n * content 单段),经 layerBoxByPath 在草稿内解析——同步完成后的父级盒(行高\n * 增长等同步效应已含)。悬空回落画布尺寸(与根层同参,position 仍自洽)。\n */\n private parentDimsInDraft(draft: Draft<Canvas>, path: LayerPath): { width: number; height: number } {\n if (isRootLayerPath(path)) return { width: draft.width, height: draft.height }\n const parentPath =\n path[path.length - 1] === 'content' ? path.slice(0, -1) : path.slice(0, -2)\n const box = layerBoxByPath(draft, parentPath as LayerPath, this.textPolicies)\n return box ?? { width: draft.width, height: draft.height }\n }\n\n /**\n * 方向键微调(kbd-nav 工单 02):选中层 position 按场景增量位移(注册表 8\n * 条目方向 × 步长归并到此,executeShortcut 传 ±1/±10)。作用面与拖动同门——\n * 选中什么微调什么,位置写法与 dragTo 同一语义(position 原位 += 增量);\n * 拖不动的空转(无选择/无文档/路径不可解析)。**不吸附**:吸附是拖动过程的\n * 连续行为(CONTEXT「微调」词条边界),微调是可预测的定量位移,不走\n * resolveSnap、不回显命中轴。\n * 连续微调合并一条历史:mergeKey 含选中层路径、方向无关——同层连按(跨方向)\n * 并成一步,undo 一次回连按前;换层即换步(mergeKey 随路径变);undo/redo\n * 走 store 既有断开规则(closeMerge)。断开是键控而非选择事件驱动:选择往返\n * (A→B→A 中途无文档变更)会并回 A 的原串,机制既有规则同款。拖动/缩放会话\n * 进行中空转(防插入事务拆分开放中的合并步,工单 07 缩放同门)。\n * 守卫(canvas-web-layer-lock 工单 01 挂账兑现):锁定子树空转(isLocked\n * 谓词同门——微调是变更类动作)。空转返回 false(executeShortcut 可用态口径)。\n */\n nudge(deltaX: number, deltaY: number): boolean {\n const path = this.store.ui.selection\n if (path === null || this.store.ui.drag !== null || this.store.ui.resize !== null) return false\n if (this.isLocked(path)) return false\n const doc = this.store.doc\n if (!doc || resolveLayer(doc, path) === null) return false\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path)\n if (!layer) return\n // immer draft 原位写 position;类型层的 readonly 由 draft 语义解除\n const position = layer.position as { x: number; y: number }\n position.x += deltaX\n position.y += deltaY\n }, { mergeKey: nudgeMergeKey(path) })\n return true\n }\n\n // ---- 参考线与标尺(ruler-guides-snap 工单 01):会话态住 ui 分支,零契约面 ----\n\n /**\n * 新增参考线(标尺拖出落线的写入口,工单 03 消费):水平/垂直轴、场景坐标。\n * 住 ui 分支——不进历史、不写 graph(ADR 0012);id 会话内自增供删除寻址。\n * 非有限坐标空转返回 null(绑定层异常输入防御,不产生通知)。\n */\n addGuide(guide: { orientation: GuideOrientation; position: number }): Guide | null {\n if (!Number.isFinite(guide.position)) return null\n const stored: Guide = { id: ++this.guideIdSeq, orientation: guide.orientation, position: guide.position }\n this.store.setGuides([...this.store.ui.guides, stored])\n return stored\n }\n\n /** 删除参考线(拖回标尺语义):按 id 移除;未知 id 空转返回 false */\n removeGuide(id: number): boolean {\n const guides = this.store.ui.guides\n const next = guides.filter((guide) => guide.id !== id)\n if (next.length === guides.length) return false\n this.store.setGuides(next)\n return true\n }\n\n /**\n * 移动参考线(抓取拖动再定位的写入口):按 id 原地改位(取向不变),\n * ui 分支同门——不进历史、不写 graph(ADR 0012),广播 guides 分支通知\n * (覆盖层失效重绘)。非有限坐标/未知 id 空转返回 false(不产生通知);\n * 同位空转返回 true(无变化不惊动订阅方——抓取未动的松手路径)。\n */\n updateGuide(id: number, position: number): boolean {\n if (!Number.isFinite(position)) return false\n const guides = this.store.ui.guides\n const target = guides.find((guide) => guide.id === id)\n if (target === undefined) return false\n if (target.position === position) return true\n this.store.setGuides(guides.map((guide) => (guide.id === id ? { ...guide, position } : guide)))\n return true\n }\n\n /**\n * 清空全部参考线(工具栏「清空参考线」写入口,工单 03 消费):ui 分支整体置空,\n * 与 removeGuide 同型广播 guides 分支通知(覆盖层失效重绘)。会话级语义同门——\n * 不进历史、不写 graph(ADR 0012),不可撤销;无文档或已空时空转(不产生通知)。\n */\n clearGuides(): void {\n if (!this.store.doc) return\n if (this.store.ui.guides.length === 0) return\n this.store.setGuides([])\n }\n\n /** 参考线列表(只读查询,呈现与吸附供轴同源) */\n listGuides(): readonly Guide[] {\n return this.store.ui.guides\n }\n\n /** 标尺显隐开关(⇧R 分派口):翻转 ui 偏好,不进历史 */\n toggleRulers(): void {\n this.store.setRulersVisible(!this.store.ui.rulersVisible)\n }\n\n // ---- 编辑器状态导出与恢复(project-data 工单 01,spec §5):与 addGuide/toggleLayerLock 同门——ui 分支写、不进历史、不写 graph ----\n\n /**\n * 导出当前打开画布的编辑器状态切片:guides 原样带出(id 是会话内自增值,\n * 宿主不持久化其语义——restore 端重排重取号)、lockedPaths 原样。无文档也可\n * 导出(ui 分支自成事实,addGuide 无 doc 守卫同门)。内核帧盲:宿主把各帧\n * 切片按帧名组装成 frames 容器,加上 schemaVersion(EDITOR_STATE_SCHEMA_VERSION)\n * 与 getEditorPrefs() 才是完整工程数据快照,经宿主层存储——容器不进内核。\n */\n exportEditorState(): CanvasEditorState {\n return { guides: this.store.ui.guides, lockedPaths: this.store.ui.lockedPaths }\n }\n\n /**\n * 恢复当前画布的状态切片(prefs 不在此——内核跨 openDocument 自持):轻结构\n * 校验(形状不合法整体拒,返 false 宿主记因)→ 整体替换 lockedPaths →\n * pruneDanglingPaths(悬空锁路径剔除——合法但内容悬空不算拒,prune 兜底)→\n * guide id 重排(清空后逐条 addGuide 重取号:快照 id 无持久语义,原样入栈必\n * 与新会话 guideIdSeq 撞号致 removeGuide 误删)。不进历史、不写 graph(红线\n * 同门)。\n */\n restoreEditorState(state: CanvasEditorState): boolean {\n const slice = parseCanvasEditorState(state)\n if (slice === null) return false\n this.store.setLockedPaths(slice.lockedPaths)\n this.pruneDanglingPaths()\n if (this.store.ui.guides.length > 0) this.store.setGuides([])\n for (const guide of slice.guides) this.addGuide(guide)\n return true\n }\n\n /**\n * 只读偏好(保存 prefs 的读取口):与 store.ui 同源,给宿主一个不用下钻\n * store 的口。读 = getEditorPrefs,写 = applyEditorPrefs,对称成对;帧切片\n * 走 restoreEditorState,两不相混。\n */\n getEditorPrefs(): EditorPrefsState {\n const ui = this.store.ui\n return {\n rulersVisible: ui.rulersVisible,\n anchorExpanded: ui.anchorExpanded,\n nextTextStyle: ui.nextTextStyle,\n }\n }\n\n /**\n * 应用偏好(会话建立时一次性,跨会话恢复 prefs 的写入口):整体替换\n * rulersVisible/anchorExpanded,不进历史;值等写入经 store 短路零通知。\n * 新层样式(text-defaults 工单 01)走宽容读逐字段合并(pickNextTextStylePatch\n * 共享谓词——与捕获同门,缺字/类型不符保留现值)。\n */\n applyEditorPrefs(prefs: EditorPrefsState): void {\n this.store.setRulersVisible(prefs.rulersVisible)\n this.store.setAnchorExpanded(prefs.anchorExpanded)\n this.store.setNextTextStyle(pickNextTextStylePatch(prefs.nextTextStyle))\n }\n\n // ---- 对齐画布(layer-align-snap 工单 01):图层盒整体对齐画布几何 ----\n\n /**\n * 对齐画布 action 族(对齐画布/贴边/贴角,ADR 0011):把 path 处图层盒整体\n * 对齐到画布几何——mode 十种:贴边 left/right/top/bottom(零边距)、居中\n * h-center/v-center、贴角 corner-tl/tr/bl/br(opts.margin 统一边距,缺省\n * ALIGN_CORNER_MARGIN_PX)。与属性面板「盒内对齐」(写 align 字段移动盒内\n * 内容)是两类动作,并存不回退。\n * 几何以 layerBoxAt(path) 解析的图层盒为唯一来源、画布尺寸取 doc.canvas;\n * 盒坐标对 position 线性(box = origin + anchorOffset + position),「目标\n * 盒坐标 − 当前盒坐标」的位移原样写 position 偏移、anchor 不动;盒大于画布\n * 不钳位(偏移可为负的领域语义,溢出侧按公式确定)。一次调用 = 一步历史\n * (无 mergeKey,连续点击不合并);已对齐(零位移)经 store 空 patch 短路\n * 不进历史;文档未打开/路径不可解析/未知 mode 空转(无历史步)。\n */\n alignToCanvas(path: LayerPath, mode: AlignToCanvasMode, options: AlignToCanvasOptions = {}): void {\n const doc = this.store.doc\n if (!doc) return\n const box = this.boxByPath(path)\n if (!box) return\n const target = alignToCanvasTarget(\n mode,\n box,\n doc.width,\n doc.height,\n options.margin ?? ALIGN_CORNER_MARGIN_PX,\n )\n if (!target) return\n const dx = target.x - box.x\n const dy = target.y - box.y\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path)\n if (!layer) return\n // draft 语义解除 readonly;盒位移与 position 位移一一对应(斜率 1)\n const position = layer.position as { x: number; y: number }\n position.x += dx\n position.y += dy\n })\n }\n\n // ---- 文本编辑会话(工单 11):会话住 ui 分支,提交经漏斗一次性落文档 ----\n\n /**\n * 进入文本编辑:路径必须解析到 TextLayer。会话只进 ui 分支(不进历史),\n * live 文本住在绑定层的 textarea,内容层随即跳绘该层文本(防重影)。\n *\n * 内容类型会话标志(canvas-web-expression-editing 工单 01,spec 决策 2)按\n * 进入瞬间 `layer.expression !== null` 锚定进会话快照——「进入时计、编辑中\n * 稳定」(verticalAnchorY 同门):编辑中面板改标记不回写标志,提交去向由\n * 快照裁决(打标是显式动作,内容判定制已否决)。同路径重复进入短路(快照\n * 不重锚);换层进入按新层现状重锚。\n */\n beginTextEdit(path: LayerPath): boolean {\n const layer = this.textLayerAt(path)\n if (layer === null) return false\n const current = this.store.ui.editing\n if (current && pathsEqual(current.path, path)) return true\n this.store.setEditing({ path, expression: layer.expression !== null })\n return true\n }\n\n /**\n * 提交漏斗的统一出口:Esc / Ctrl+Enter / blur / 点画布他处四条退出路径都收拢\n * 到这里。先清会话再落文档——退出路径可能级联触发(画布点按的 pointerdown\n * 提交与随后 blur 竞态),会话清空后后续调用幂等,保证一次退出恰一步历史。\n *\n * 提交矩阵(canvas-web-expression-editing 工单 01,spec 决策 3,空串判定先于\n * 片段判定):\n * - 串与文档一致:不进历史(进出编辑零噪声),返回 false——对比对象随会话\n * 模式取:表达式会话对比 `layer.expression`、字面会话对比 `layer.text`(现行\n * 语义);\n * - 空串:删除该图层(复用 deleteLayer 的 splice/重映射语义),任何模式同语义;\n * - 表达式会话且串含 ≥1 语法有效闭合片段(hasValidExpressionFragment):\n * updateDataExpression 标记写(保持标记、text 自动重镜像);\n * - 其余(字面会话恒走此支;表达式会话无合法片段回落):updateData 字面写、\n * 字面接管解标——含 `{{...}}` 按字面保留,杜绝无片段标记层。\n */\n commitTextEdit(text: string): boolean {\n const editing = this.store.ui.editing\n if (!editing) return false\n this.store.setEditing(null)\n\n const layer = this.textLayerAt(editing.path)\n if (!layer) return false\n if (editing.expression ? text === layer.expression : text === layer.text) return false\n if (text === '') {\n this.deleteLayer(editing.path)\n return true\n }\n if (editing.expression && hasValidExpressionFragment(text)) {\n this.updateDataExpression(editing.path, text)\n return true\n }\n this.updateData(editing.path, text)\n return true\n }\n\n /**\n * 编辑 overlay 的布局描述:与绘制/命中同一套布局策略求出的盒几何与排版字段\n * (textarea 以盒左上对位、盒尺寸占位,字号/行高/内边距按场景像素交给 CSS\n * transform 缩放)。verticalAnchorY 按进入编辑时的文档文本计算,编辑中保持\n * 稳定不随键入跳动。非文本层/路径失效返回 null。\n */\n textEditLayout(path: LayerPath): TextEditLayout | null {\n const layer = this.textLayerAt(path)\n const doc = this.store.doc\n if (!layer || !doc) return null\n const box = this.boxByPath(path)\n if (!box) return null\n const editing = this.store.ui.editing\n return {\n box,\n text: layer.text,\n font: layer.font,\n fontColor: layer.fontColor,\n fontSize: layer.fontSize,\n lineHeightPx: lineHeightPx(layer),\n padding: layer.shape.padding,\n horizontalAlign: layer.align.horizontal,\n verticalAlign: layer.align.vertical,\n verticalAnchorY: textOrigin(layer, this.textPolicies).y,\n autowrap: layer.autowrap,\n expressionMode:\n editing !== null && pathsEqual(editing.path, path)\n ? editing.expression\n : layer.expression !== null,\n }\n }\n\n /** 编辑会话的目标图层查询:路径须解析到 TextLayer,其余(含无文档)返回 null */\n private textLayerAt(path: LayerPath): TextLayer | null {\n const doc = this.store.doc\n const layer = doc ? resolveLayer(doc, path) : null\n return layer && layer.type === 'TextLayer' ? layer : null\n }\n\n // ---- 查找替换(canvas-web-find-replace 工单 01):会话态住 ui 分支,扫描/替换纯函数在 editing/findReplace ----\n\n /**\n * 打开查找会话(⌘F 分派口):无文档空转返回 false;已开时值等短路零通知\n * (重复 ⌘F 的重新聚焦归绑定层)。查询词/替换词/游标跨关开保留(closeFind\n * →beginFind 同文档连续改字),openDocument 才整体重置。\n */\n beginFind(): boolean {\n if (!this.store.doc) return false\n this.store.setFind({ ...this.store.ui.find, open: true })\n return true\n }\n\n /** 关闭查找会话(Esc 归面板组件调用):仅翻开合面,查询词与游标保留 */\n closeFind(): void {\n this.store.setFind({ ...this.store.ui.find, open: false })\n }\n\n /** 查找词写入(查找条输入即扫的源头):查询词变更即重置游标(旧下标对新命中集无意义) */\n setFindQuery(query: string): void {\n const find = this.store.ui.find\n this.store.setFind({ ...find, query, cursor: find.query === query ? find.cursor : 0 })\n }\n\n /** 替换词写入 */\n setFindReplacement(replacement: string): void {\n this.store.setFind({ ...this.store.ui.find, replacement })\n }\n\n /** 游标写入(上一个/下一个导航口):存原值,越界在读侧钳位 */\n setFindCursor(cursor: number): void {\n this.store.setFind({ ...this.store.ui.find, cursor })\n }\n\n /**\n * 派生命中列表(overlay 高亮与查找条计数同源消费):每次从 doc + query 现算、\n * 不驻留(树小无压力,免 pruneDanglingPaths 式失配维护);视觉序 = 数组尾→头\n * (hitTest 同门),命中 = path + 偏移对。\n */\n listFindMatches(): readonly FindTextHit[] {\n const doc = this.store.doc\n if (!doc) return []\n return scanTextMatches(doc, this.store.ui.find.query)\n }\n\n /** 当前游标(读侧钳位到派生命中列表:越界落末处/首处,空命中为 0) */\n get findCursor(): number {\n return clampFindCursor(this.store.ui.find.cursor, this.listFindMatches().length)\n }\n\n /**\n * 替换当前命中(查找条「替换」按钮):一次无 mergeKey 事务 = 一步历史;游标\n * 原地不动——事务后派生列表在被替换处缩一位,原下标即「下一处」(「替换后\n * 自动跳下一处」靠派生天然成立,末处替换则钳位停在尾)。写值经 replaceHitsInDraft\n * 与文本编辑提交同门(text 直写 + expression 保持 null)。无文档/空 query/\n * 无命中空转返回 false。\n */\n replaceOne(): boolean {\n const doc = this.store.doc\n const query = this.store.ui.find.query\n if (!doc || query === '') return false\n const matches = scanTextMatches(doc, query)\n const hit = matches[clampFindCursor(this.store.ui.find.cursor, matches.length)]\n if (!hit) return false\n this.store.transact((draft) => {\n replaceHitsInDraft(draft, [hit], this.store.ui.find.replacement)\n })\n return true\n }\n\n /**\n * 全部替换(查找条「全部替换」按钮):命中(path + 偏移对)在事务前按原文\n * 快照一次算定,单事务内按同字段偏移降序应用、替换产物不重扫(replacement\n * 含查询串不死循环)——一次调用 = 一步历史,undo 一次全回。返回应用处数\n * (计数反馈「已替换 N 处」);无文档/空 query 空转返回 0。\n */\n replaceAll(): number {\n const doc = this.store.doc\n const query = this.store.ui.find.query\n if (!doc || query === '') return 0\n const hits = scanTextMatches(doc, query) // 原文快照:命中事务前一次算定\n if (hits.length === 0) return 0\n let applied = 0\n this.store.transact((draft) => {\n applied = replaceHitsInDraft(draft, hits, this.store.ui.find.replacement)\n })\n return applied\n }\n\n // ---- 属性写入(工单 09):面板零直改,一切文档字段编辑经这三个 action ----\n\n /**\n * 更新图层 spec 子树字段(属性面板的权威写入口):key 为图层内字段路径\n * (领域形态,如 ['shape','backgroundColor']、['position','x']、单段 ['fontSize']),\n * 拼上图层路径即 patch path。数值收整/钳位等取值策略由注册表层负责,内核透传。\n * 路径无法解析或字段悬空时静默空转;连续输入经 mergeKey 合并为一步历史。\n *\n * 表格子树的权威收口(工单 12):格 autoHeight 是带重算的切换(采纳内容动态高\n * 并固化,非裸标志写);其余写入后按解码强同步的不变量重断言(行宽=表宽、\n * 内容宽/高重同步、行高取最高格)——往返恒等在任何字段编辑后不破。\n */\n updateSpec(path: LayerPath, key: readonly string[], value: unknown, options: TransactOptions = {}): void {\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path)\n if (!layer) return\n // 模板格跳过采纳语义(决策 2026-09):模板态高度耦合全豁免(ADR 0006,\n // 解码对声明高/autoHeight 原样保留、仅 auto 标志清零声明高),下方裸写 +\n // 归一即解码同门;非模板格的带重算切换不适用\n if (\n layer.type === 'TableCellLayer'\n && !isTemplateSubtreePath(path)\n && key.length === 2\n && key[0] === 'shape'\n && key[1] === 'autoHeight'\n ) {\n setCellAutoHeightInDraft(draft, path, Boolean(value), this.textPolicies)\n return\n }\n // draft 语义解除 readonly;字段形态由注册表与领域类型把关,内核透传\n writeSpecField(layer, key, value)\n // 解码不变量:autoHeight 置位即声明高归零(renormalizeAutoHeightInDraft\n // 收口),否则保存再打开声明高被清,往返恒等破。\n renormalizeAutoHeightInDraft(layer)\n canonicalizeTableSyncInDraft(draft, path, key, this.textPolicies)\n }, options)\n this.captureNextTextStyle(path, key, value)\n }\n\n /**\n * 新层样式捕获(text-defaults 工单 01,CONTEXT「新层样式」词条):属性面板\n * 对 TextLayer 文本族五字段的权威写顺带更新偏好槽——「用户改过一次,下一个\n * 新层沿用」。捕获口唯一(updateSpec 漏斗):内容写(updateData)、样式粘贴\n * (搬运不是偏好)、撤销重做都不经过此口。字段合法性走 editing 域共享谓词\n * pickNextTextStylePatch(与 prefs 宽容读同门,评审合流不再双写):目标层非\n * TextLayer、键不命中或值类型不符即空转,符合字段经值等短路入槽。\n */\n private captureNextTextStyle(path: LayerPath, key: readonly string[], value: unknown): void {\n const layer = this.store.doc ? resolveLayer(this.store.doc, path) : null\n if (!layer || layer.type !== 'TextLayer') return\n const field = captureFieldOfKey(key)\n if (field === null) return\n const patch = pickNextTextStylePatch({ [field]: value })\n if (patch[field] !== undefined) this.store.setNextTextStyle(patch)\n }\n\n /**\n * 更新图层数据字段(wire data.value 的领域展开,按 type 分派):\n * TextLayer → text、ImageLayer → src、QrCodeLayer → value;\n * 空串/null 归空语义与解码逐条对齐(Image null、Text/Qr 空串)。\n * 字面写解除标记(工票 02,镜像 PHP 三内容层 setter):写值即置 expression =\n * null,值与标记的镜像关系随之解除——标记态文档经字面编辑退化为字面态,\n * 不可自动恢复属预期(spec §3.7 兼容面)。\n * 表/行/格无数据字段,空转。\n */\n updateData(path: LayerPath, value: string | null, options: TransactOptions = {}): void {\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path) as Draft<Layer> | null\n if (!layer) return\n switch (layer.type) {\n case 'TextLayer':\n layer.text = value == null ? '' : String(value)\n layer.expression = null\n break\n case 'ImageLayer':\n layer.src = value == null || value === '' ? null : String(value)\n layer.expression = null\n break\n case 'QrCodeLayer':\n layer.value = value == null ? '' : String(value)\n layer.expression = null\n break\n default:\n break\n }\n }, options)\n }\n\n /**\n * 数据字段表达式标记写入口(打标/解标,spec:canvas-web-expression-marking §1;\n * 镜像 PHP 三内容层 setExpression 与 decode 标记/未标记分支):\n * - expression 非 null = 打标:expression 原文 + 值字段恒镜像原文(Text.text /\n * Image.src / QrCode.value);标记态不做空串 →null 归一(decodeImageLayer\n * 标记分支同门)。\n * - expression = null = 解标:值字段保持现值退字面,`expression = null`;\n * 字面态空值归一交给 updateData/decode 分支(这里不清值,字面接管语义)。\n * - 仅三内容层生效,表/行/格/行模板空转(与 updateData 同门)。\n * - updateData 语义不变:字面写解除标记仍成立(spec §1 明示两者并排)。\n */\n updateDataExpression(path: LayerPath, expression: string | null, options: TransactOptions = {}): void {\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path) as Draft<Layer> | null\n if (!layer) return\n switch (layer.type) {\n case 'TextLayer':\n layer.expression = expression == null ? null : String(expression)\n if (layer.expression !== null) layer.text = layer.expression\n break\n case 'ImageLayer':\n layer.expression = expression == null ? null : String(expression)\n if (layer.expression !== null) layer.src = layer.expression\n break\n case 'QrCodeLayer':\n layer.expression = expression == null ? null : String(expression)\n if (layer.expression !== null) layer.value = layer.expression\n break\n default:\n break\n }\n }, options)\n }\n\n /** 更新画布级字段(未选中图层时面板的宽/高写入口);键由类型收窄为 width/height */\n updateCanvasProp(key: 'width' | 'height', value: number, options: TransactOptions = {}): void {\n this.store.transact((draft) => {\n draft[key] = value\n }, options)\n }\n\n // ---- 数据源 schema 声明(content-completion 工单 03/08):D2 宿主随会话注入 ----\n\n /**\n * 注入数据源 schema 声明(载荷形态,D1 钉定:声明即 compile(canvas, dataset)\n * 收到的 data 载荷形状,根上下文候选 = 载荷顶层键)。声明经\n * normalizeExpressionSchemaSource 一次性编译收口——根级结构性问题(保留键\n * row/$ 前缀,前移填充期 reserved_root_key;根非对象/缺 properties 层级)降级\n * 为无候选 + console 警告,局部故障($ref 断链/外部指针/环引用/目标形态不符)\n * 不拒绝:该节点降叶子 + 注入期汇总告警一次,树其余部分照常服务,不抛错不\n * 弹错(辅助声明不作权威);null/undefined = 清除声明。声明只住 store ui 分支:\n * 不进 graph、不落 localStorage、不进 wire;openDocument 换文档不重置,\n * 重注入/清除走同一入口。签名与「注入即整体替换」语义不变(D6)。\n */\n setDataSourceSchema(raw: unknown): void {\n this.store.setDataSourceSchema(\n raw === null || raw === undefined ? null : normalizeExpressionSchemaSource(raw),\n )\n }\n\n // ---- 上传(工单 13):本机资源 → 可物化引用,实现经 uploadHandler 注入 ----\n\n /** 上传可用性:未注入 uploadHandler 时为 false(宿主据此隐藏/禁用上传入口) */\n get canUpload(): boolean {\n return this.uploadHandler !== null\n }\n\n /** 字体清单(内置可配置 + 自定义上传追加;清单 ≠ 物化,加载走渲染端物化管线) */\n get fontCatalog(): FontCatalog {\n return this.fontCatalogValue\n }\n\n /**\n * 本机图片 → 新增图片图层并写入可物化引用(全流程 = 一步历史):上传经注入\n * 实现,引用与 addRootLayerInDraft 落同一事务(撤销一次即回退整个上传建层),\n * 完成后自动选中新层。文档未打开直接返回 null(不产生上传副作用——字节不离开\n * 本机);上传失败异常上抛、文档不动。placement(kbd-nav 工单 05)缺省 =\n * 编辑器缺省盒;`at` 图层盒左上角对准场景点、`size` 1:1 落盒不缩放(拖文件\n * 入画布语义,自然尺寸由绑定层解码传入——内核无 DOM)。\n */\n async uploadImageAsLayer(file: UploadFile, placement: UploadImagePlacement = {}): Promise<LayerPath | null> {\n if (!this.store.doc) return null\n const ref = await this.requireUpload(file)\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n index.v = addRootLayerInDraft(draft, 'ImageLayer')\n if (index.v < 0) return\n const layer = draft.layers[index.v] as Draft<ImageLayer>\n layer.src = ref\n if (placement.at) layer.position = { ...layer.position, x: placement.at.x, y: placement.at.y }\n if (placement.size) {\n layer.shape.width = placement.size.width\n layer.shape.height = placement.size.height\n }\n })\n if (index.v < 0) return null\n const path: LayerPath = ['layers', index.v]\n this.store.setSelection(path)\n return path\n }\n\n /**\n * 本机图片 → 可物化引用(裸上传,不建层不写文档):引用落到哪个图片层由\n * 消费方决定(属性面板图片控件),与 uploadFont 同门的 bare 形态。文档未打开\n * 直接返回 null(不产生上传副作用——字节不离开本机);未注入 uploadHandler\n * 抛 UploadHandlerMissingError;handler 失败异常上抛。\n */\n async uploadImage(file: UploadFile): Promise<string | null> {\n if (!this.store.doc) return null\n return this.requireUpload(file)\n }\n\n /**\n * 本机字体 → 可物化引用并加入字体清单(自定义条目,上传后即可选可用)。\n * 不写文档:引用落到哪个文本层由宿主/绑定层决定(属性面板字体控件)。\n */\n async uploadFont(file: UploadFile): Promise<string> {\n const ref = await this.requireUpload(file)\n this.fontCatalogValue.addCustom({ label: uploadDisplayName(file), ref })\n return ref\n }\n\n private requireUpload(file: UploadFile): Promise<string> {\n if (this.uploadHandler === null) return Promise.reject(new UploadHandlerMissingError())\n return this.uploadHandler(file)\n }\n\n // ---- 图层面板结构编辑(工单 10):重排/增删走单一 action,重排两套语义分立 ----\n\n /**\n * 根层重排(图层面板拖动):面板坐标(0 = 视觉最上层),to 为 insert-before\n * 落点。数组序 splice + 移动层 priority 中点插值(保持「数组按 priority 降序」\n * 的契约语义,保存再打开顺序不变),一次调用 = 一步历史。语义细节见\n * layerPanel.moveRootLayerInDraft。\n */\n moveRootLayer(fromPanel: number, toPanel: number): void {\n if (!this.store.doc) return\n const moved: TxOut<{ from: number; to: number } | null> = { v: null }\n this.store.transact((draft) => {\n moved.v = moveRootLayerInDraft(draft, fromPanel, toPanel)\n })\n if (moved.v === null) return\n this.remapPathSlices([], 'layers', moved.v.from, moved.v.to)\n }\n\n /**\n * 表格行重排(图层面板行拖动):直接改 rows 数组序(容器内嵌套数组序语义,\n * priority 不参与,与根层语义分立),一次调用 = 一步历史。行内后代的选择/\n * 悬停路径随行重映射。\n */\n moveTableRow(tablePath: LayerPath, fromRow: number, toRow: number): void {\n if (!this.store.doc) return\n const moved: TxOut<{ from: number; to: number } | null> = { v: null }\n this.store.transact((draft) => {\n moved.v = moveTableRowInDraft(draft, tablePath, fromRow, toRow)\n })\n if (moved.v === null) return\n this.remapPathSlices(tablePath, 'rows', moved.v.from, moved.v.to)\n }\n\n /**\n * 新增根层(图层面板「新增」入口):工厂缺省形态 + 置顶 priority = min − 1\n * (空画布 0),push 到数组尾(视觉最上层)并自动选中新层;一次调用 = 一步历史。\n */\n addRootLayer(type: LayerType): void {\n if (!this.store.doc) return\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n // 新层样式槽覆盖工厂缺省(text-defaults 工单 01):工厂仅 TextLayer 臂消费\n index.v = addRootLayerInDraft(draft, type, this.store.ui.nextTextStyle)\n })\n if (index.v >= 0) this.store.setSelection(['layers', index.v])\n }\n\n /**\n * 删除图层(含子树):根层整删、行/格 splice、格内容置 null(语义见\n * layerPanel.deleteLayerInDraft);选中/悬停落在被删子树内即清空,其余路径\n * 平移重映射。一次调用 = 一步历史。\n * 模板子树删除面(spec §3.2):cells/content 两级放行(内核两型 union),\n * 仅行模板替身(template 收尾路径)拦截——删行模板 = 表退出模板态,归转换\n * 入口(convertTableToRows),不混入删除。\n * 锁定 guard(canvas-web-layer-lock 工单 01):路径落在锁定子树(锁定的根层\n * 及其行/格/内容)即静默空转——Delete/⌫ 与面板删除按钮的权威挡点,无历史步。\n */\n deleteLayer(path: LayerPath): void {\n if (!this.store.doc || path[path.length - 1] === 'template') return\n if (this.isLocked(path)) return\n const removed: TxOut<DeletedLayerRef | null> = { v: null }\n this.store.transact((draft) => {\n removed.v = deleteLayerInDraft(draft, path)\n })\n if (removed.v === null) return\n if (removed.v.kind === 'list') {\n this.remapPathSlices(removed.v.containerPath, removed.v.key, removed.v.index, removed.v.index)\n return\n }\n // 格内容删除:无下标平移,命中该路径的选择/悬停清空\n if (pathsEqual(this.store.ui.selection, removed.v.path)) this.store.setSelection(null)\n if (pathsEqual(this.store.ui.hovered, removed.v.path)) this.store.setHovered(null)\n }\n\n /**\n * 重命名根图层(layer-panel-ux 工单 09):一次调用 = 一步历史(可撤销)。\n * 仅根图层——行/格是容器内结构(显示层「行 N / 格 N」派生标签),路径非根\n * 或不可解析一律空转(无历史步);空名 = 回退缺省(文档 name 归空串,\n * wire 键随之省略,显示层回落派生标签)。不改结构与 priority,选择/悬停\n * 路径不受影响。\n */\n renameLayer(path: LayerPath, name: string): void {\n if (!this.store.doc || !isRootLayerPath(path)) return\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path) as Draft<Layer> | null\n if (!layer) return\n layer.name = name\n })\n }\n\n /**\n * 开始重命名会话(图层面板双击/铅笔/F2 的统一入口):路径必须解析到根图层。\n * 会话只进 ui 分支(不进历史),输入框的草稿文本住绑定层,提交经\n * commitRename 漏斗一次性落文档。\n */\n beginRename(path: LayerPath | null): boolean {\n if (path === null || !this.store.doc || !isRootLayerPath(path)) return false\n if (resolveLayer(this.store.doc, path) === null) return false\n this.store.setRenaming(path)\n return true\n }\n\n /**\n * 重命名提交漏斗的统一出口:Enter / blur 两条退出路径都收拢到这里。先清\n * 会话再落文档——退出路径可能级联触发(Enter 提交后 input 卸载的 blur),\n * 会话清空后后续调用幂等,保证一次退出恰一步历史。空名直传 renameLayer\n * (回退缺省语义在内核);与现名相同不产生历史步(进出编辑零噪声)。\n */\n commitRename(name: string): boolean {\n const renaming = this.store.ui.renaming\n if (renaming === null) return false\n this.store.setRenaming(null)\n const layer = this.store.doc ? resolveLayer(this.store.doc, renaming) : null\n if (!layer || layer.name === name) return false\n this.renameLayer(renaming, name)\n return true\n }\n\n /** 取消重命名会话:清 ui 态不落文档(Esc 语义) */\n cancelRename(): void {\n this.store.setRenaming(null)\n }\n\n /**\n * 切换根图层显示/隐藏(layer-panel-ux 工单 10):一次调用 = 一步历史(可撤销)。\n * 仅根图层——visible 在 LayerBase 面(渲染端整层跳过,行/格是容器内结构无独立\n * 可见性),路径非根或不可解析一律空转(无历史步),与 renameLayer 同门。\n * 隐藏的下游语义:渲染跳过(工单 01 契约)+ 命中/gizmo 过滤(spatial/hitTest、\n * 绑定层 gizmo),编辑器 UI 只消费这里的写入口。\n */\n toggleLayerVisibility(path: LayerPath): void {\n if (!this.store.doc || !isRootLayerPath(path)) return\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path) as Draft<Layer> | null\n if (!layer) return\n layer.visible = !layer.visible\n })\n }\n\n /**\n * 切换根图层锁定(canvas-web-layer-lock 工单 01):会话级锁定态,ui 分支\n * 变更——不进历史、不写 graph、wire 零键(红线 3 同门),openDocument 重置。\n * 仅根图层——行/格是容器内结构(锁定语义只在 LayerBase 面),路径非根或\n * 不可解析一律空转(renameLayer/toggleLayerVisibility 同门)。\n * 下游语义:锁定层整子树退出画布命中面(hitTest 过滤)、拖动起点/删除在\n * 内核空转(方向键微调已随 kbd-nav 工单 02 接入同一谓词);属性编辑/duplicate/\n * 显隐/改名/z 序等刻意通道不受限;⌘D 副本新路径天然无锁。\n */\n toggleLayerLock(path: LayerPath): void {\n if (!this.store.doc || !isRootLayerPath(path)) return\n if (resolveLayer(this.store.doc, path) === null) return\n const current = this.store.ui.lockedPaths\n const next = current.some((locked) => pathsEqual(locked, path))\n ? current.filter((locked) => !pathsEqual(locked, path))\n : [...current, path]\n this.store.setLockedPaths(next)\n }\n\n /**\n * 锁定谓词(canvas-web-layer-lock 工单 01,ui 分支读取口):路径落在锁定\n * 子树即 true,判定收口 isLockedPath——hitTest 过滤、动作 guard、gizmo、\n * 面板投影共用,绑定层不得自行手写锁定路径比对。\n */\n isLocked(path: LayerPath | null): boolean {\n return path !== null && isLockedPath(path, this.store.ui.lockedPaths)\n }\n\n // ---- 剪贴板与置顶/置底(工单 14):子树深拷贝语义见 clipboard.ts ----\n\n /**\n * 复制当前选中层:子树深拷贝快照进会话剪贴板,并捕获复制时点的绝对盒(粘贴\n * 落位基准)与源路径(sourcePath——粘贴时紧邻插入的解析凭据,alt-drag-paste\n * 工单 02)。源须为可落根层的类型(行/格是容器内结构,v1 不可复制);无选择/\n * 不可复制为 false。\n */\n copySelection(): boolean {\n if (!this.canCopySelection) return false\n const path = this.store.ui.selection!\n const doc = this.store.doc!\n const box = this.boxByPath(path)\n if (!box) return false\n this.clipboardEntry = {\n layer: cloneLayerSubtree(resolveLayer(doc, path)!),\n sourceBox: box,\n sourcePath: path,\n }\n return true\n }\n\n /** 复制可用态(右键菜单「副本」的可用判定与 copySelection 同一语义) */\n get canCopySelection(): boolean {\n const path = this.store.ui.selection\n return path !== null && this.canCopyLayerAt(path)\n }\n\n /**\n * 路径处图层可复制(alt-drag-paste 工单 01,Alt+拖 copyMode 的绑定层判定面):\n * 可解析且类型可落根层(canCopyLayerAt 同门——行/格是容器内结构不可复制,\n * 格内容可复制)。无文档为 false。\n */\n canCopyLayerAt(path: LayerPath): boolean {\n const doc = this.store.doc\n return doc !== null && canCopyLayerAtPath(doc, path)\n }\n\n /**\n * 粘贴剪贴板(alt-drag-paste 工单 02 真原位):子树深拷贝插入根层——落点 =\n * 复制时点绝对盒、零偏移(重复粘贴同位叠放;锚点补偿保留:dx=0 时根层源\n * position 恒等原位、格内容源落其视觉位置);z 序紧邻源层(sourcePath 解析\n * 与退化置顶见 insertRootCopy);自动选中新层;一次调用 = 一步历史。空剪贴板\n * 为 null。每次粘贴从剪贴板快照重新克隆:粘贴产物之间以及与源文档都不共享\n * 引用。\n */\n pasteFromClipboard(): LayerPath | null {\n const entry = this.clipboardEntry\n const doc = this.store.doc\n if (!entry || !doc) return null\n const prepared = prepareRootPaste(entry.layer, entry.sourceBox, doc.width, doc.height, 0, 0)\n return this.insertRootCopy(prepared, entry.sourcePath)\n }\n\n /**\n * 创建副本 = 复制当前选中层 + 固定一格偏移(+20)+ 置顶 + 自动选中,一步历史。\n * 落位以当前绝对盒为基准(连续副本基于选中的副本链式偏移,天然不重叠)。\n * **不覆盖剪贴板**(先复制 A 再副本 B,粘贴仍出 A)。无选择/不可复制为 null。\n */\n duplicateSelection(): LayerPath | null {\n const path = this.store.ui.selection\n const doc = this.store.doc\n if (!path || !doc || !canCopyLayerAtPath(doc, path)) return null\n const box = this.boxByPath(path)\n if (!box) return null\n const prepared = prepareRootPaste(\n resolveLayer(doc, path)!,\n box,\n doc.width,\n doc.height,\n PASTE_OFFSET_PX,\n PASTE_OFFSET_PX,\n )\n return this.insertRootCopy(prepared)\n }\n\n /**\n * 粘贴/副本的公共落库尾:根层插入 + 自动选中新层;一次调用 = 一步历史。\n * source 给定时先试紧邻源层(源仍可解析且为根层——alt-drag-paste 工单 02 的\n * ⌘V 紧邻消费;路径寻址尽力而为:删源后同下标兄弟层/他文档同下标层会顶替\n * 解析,紧邻彼层是可接受的近似,工单 Comments 记档)——原语返回 null 或条件\n * 不满足回落置顶;source 缺省恒置顶(⌘D 语义不动)。\n */\n private insertRootCopy(prepared: Layer, source?: LayerPath): LayerPath | null {\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n index.v =\n source !== undefined && resolveLayer(draft, source) !== null && isRootLayerPath(source)\n ? insertRootLayerAdjacentInDraft(draft, prepared as Draft<Layer>, source) ?? -1\n : -1\n if (index.v < 0) index.v = insertRootLayerInDraft(draft, prepared as Draft<Layer>)\n })\n if (index.v < 0) return null\n const path: LayerPath = ['layers', index.v]\n this.store.setSelection(path)\n return path\n }\n\n // ---- 样式粘贴(canvas-web-style-paste 工单 01):样式集/适用面常量与快照/落地纯函数在 editing/styleClipboard ----\n\n /**\n * 复制样式(⌥⌘C / 右键菜单):按源类型适用面抓逐字段深拷贝快照进会话私有\n * 槽({sourceType, values})。源为**任意可解析路径**(根/行/格/格内容/模板\n * 子树皆可);无文档/路径不可解析/源无样式面(行模板替身,适用面为空)空转\n * 返回 false——空快照不占槽,已拷贝的样式不被无样式源清掉。拷贝不进历史\n * (剪贴板态不是文档态)。\n */\n copyStyle(path: LayerPath): boolean {\n const doc = this.store.doc\n const layer = doc ? resolveLayer(doc, path) : null\n if (!layer) return false\n const snapshot = captureStyleSnapshot(layer)\n if (!snapshot) return false\n this.styleClipboardEntry = snapshot\n return true\n }\n\n /** 复制可用态(右键菜单):有可解析选择且源类型有样式面 */\n get canCopyStyle(): boolean {\n const path = this.store.ui.selection\n const doc = this.store.doc\n if (path === null || doc === null) return false\n const layer = resolveLayer(doc, path)\n return layer !== null && STYLE_FIELD_KEYS_BY_TYPE[layer.type].length > 0\n }\n\n /**\n * 粘贴样式(⌥⌘V / 右键菜单):按目标类型适用面 ∩ 快照逐字段 verbatim 覆盖\n * (null 即值、Border 整对象;快照没有的字段 = 源不适型,静默跳过)。目标为\n * **任意可解析路径**——模板子树与格内容放行(尺寸字段不在样式集,行/格/\n * 内容宽高强同步天然不冲突);锁定语义天然放行(属性写通道,CONTEXT「锁定」\n * 词条)。单事务内循环写入 + autoHeight⟹height=0 断言 + canonicalizeTableSync\n * 一次收口(updateSpec 同门)——一次粘贴 = 一步历史,连续粘贴不合并(无\n * mergeKey);全等字段跳写、整事务零变化经 store 空 patch 短路不进历史;\n * 无文档/空槽/路径不可解析空转返回 false。\n */\n pasteStyle(path: LayerPath): boolean {\n const entry = this.styleClipboardEntry\n if (!entry || !this.store.doc) return false\n const written: TxOut<readonly (readonly string[])[]> = { v: [] }\n this.store.transact((draft) => {\n const layer = resolveLayer(draft, path) as Draft<Layer> | null\n if (!layer) return\n const keys = applyStyleFieldsInDraft(layer, entry)\n if (keys.length === 0) return\n // 解码不变量重断言(updateSpec 同门,一次收口):autoHeight⟹声明高归零\n // 经 renormalizeAutoHeightInDraft 收口;表格强同步按末写字段重断言——\n // 样式字段不触宽度耦合(根表分支天然空转),容器目标走内容/格/行\n // 重同步收口\n renormalizeAutoHeightInDraft(layer)\n canonicalizeTableSyncInDraft(draft, path, keys[keys.length - 1]!, this.textPolicies)\n written.v = keys\n })\n return written.v.length > 0\n }\n\n /** 粘贴可用态(右键菜单):样式槽非空且有可解析选择;交集为零变化的空转在动作面兜底 */\n get canPasteStyle(): boolean {\n const path = this.store.ui.selection\n const doc = this.store.doc\n return this.styleClipboardEntry !== null && path !== null && doc !== null && resolveLayer(doc, path) !== null\n }\n\n /**\n * z 序四件套(右键菜单语义 + kbd-nav 工单 01 键位):根层重排,v1 只作用根层\n * (行/格是数组序语义)。全部复用 moveRootLayer(priority 中点插值同款——\n * 置顶 = min−1、置底 = max+1、无整数间隙全表归一化兜底),一次调用 = 一步\n * 历史;已在目标端为无操作(不产生历史步)。返回是否发生移动(executeShortcut\n * 的可用态口径;右键菜单直调忽略返回值)。锁定层 z 序放行(锁定谓词的刻意\n * 通道,canvas-web-layer-lock 工单 01 同口径)。\n */\n bringToFront(): boolean {\n const panel = this.selectedRootPanelIndex()\n if (panel === null || panel === 0) return false\n this.moveRootLayer(panel, 0)\n return true\n }\n\n sendToBack(): boolean {\n const panel = this.selectedRootPanelIndex()\n const count = this.store.doc?.layers.length ?? 0\n if (panel === null || panel === count - 1) return false\n this.moveRootLayer(panel, count)\n return true\n }\n\n /**\n * 前移/后移一格(kbd-nav 工单 01):面板序号 ±1 的单格重排——与置顶/置底、\n * 面板拖动同一 moveRootLayer 语义(中点插值优先、无间隙全表归一化兜底),仅\n * 根层(isRoot 同门),已最前/最后空转。连按各成一步历史(无 mergeKey,\n * shortcut action 不合步的先例)。落点按「insert-before 原始序号」折算:\n * 前移 = 插到原 panel−1 层之前(to = panel−1),后移 = 插到原 panel+1 层之后\n * (to = panel+2)。\n */\n bringForward(): boolean {\n const panel = this.selectedRootPanelIndex()\n if (panel === null || panel <= 0) return false\n this.moveRootLayer(panel, panel - 1)\n return true\n }\n\n sendBackward(): boolean {\n const panel = this.selectedRootPanelIndex()\n const count = this.store.doc?.layers.length ?? 0\n if (panel === null || panel >= count - 1) return false\n this.moveRootLayer(panel, panel + 2)\n return true\n }\n\n /** 选中根层的面板序号(0 = 视觉最上层);非根层选择/无文档为 null */\n private selectedRootPanelIndex(): number | null {\n const path = this.store.ui.selection\n const doc = this.store.doc\n if (!path || !doc || !isRootLayerPath(path)) return null\n return doc.layers.length - 1 - (path[1] as number)\n }\n\n /**\n * 可分派的选中根层路径(⇧⌘L/⇧⌘H 分派口守卫,工单 02):无选择/无文档/非根\n * 形状/悬空(不可解析)一律 null——分派口「空转返回 false」与内核 action 的\n * 静默空转同口径(悬空窗口来自 prune 之外的结构步,兜底裁定可用态)。\n */\n private selectableRootPath(): LayerPath | null {\n const path = this.store.ui.selection\n const doc = this.store.doc\n if (path === null || doc === null || !isRootLayerPath(path)) return null\n return resolveLayer(doc, path) === null ? null : path\n }\n\n // ---- 快捷键分派(工单 14):注册表分类(shortcuts.ts)→ 这里执行 ----\n\n /**\n * 执行快捷键动作(注册表分类的出口):undo/redo/delete/copy/paste/duplicate/\n * copyStyle/pasteStyle(⌥⌘C/⌥⌘V,canvas-web-style-paste 工单 01)/\n * rename/findReplace(⌘F,canvas-web-find-replace 工单 01)/\n * toggleRulers/toggleLayerLock/toggleLayerVisibility 的统一分派面,以及\n * z 序四件套与缩放三件(kbd-nav 工单 01:bringForward/sendBackward/\n * bringToFront/sendToBack/zoomReset/fitToSurface/fitToSelection)、微调八动作\n * (kbd-nav 工单 02:nudgeUp/Down/Left/Right + Coarse 变体,归并到同一 nudge\n * 实现)与循环选层两动作(kbd-nav 工单 03:selectNextLayer/selectPrevLayer);\n * helpShortcuts(kbd-nav 工单 04)是 UI 面动作,由绑定层桥拦截路由\n * 帮助面板、不经此处。让路规则在\n * 分类器(classifyEditorShortcut)裁决,到达这里的动作不再重复判态;动作为\n * 空转(无选择/空剪贴板/非根层/已在端点)返回 false,其余 true。rename 开\n * 选中根层的重命名会话(F2,工单 09),不直接写文档;锁定/显隐作用于选中根层\n * (canvas-web-layer-lock 工单 02 + feature-status 显隐键位挂账补位),非根/\n * 无选择按可用态裁剪返回 false;相机三件不进历史、恒 true(surface 未知时\n * 内核静默空转,同 toggleRulers 的无条件口径)。\n */\n executeShortcut(action: EditorShortcutAction): boolean {\n switch (action) {\n case 'undo':\n this.undo()\n return true\n case 'redo':\n this.redo()\n return true\n case 'copy':\n return this.copySelection()\n case 'paste':\n return this.pasteFromClipboard() !== null\n case 'duplicate':\n return this.duplicateSelection() !== null\n case 'copyStyle': {\n // 样式粘贴(canvas-web-style-paste 工单 01):⌥⌘C 分派口,无选择空转\n const path = this.store.ui.selection\n return path !== null && this.copyStyle(path)\n }\n case 'pasteStyle': {\n // ⌥⌘V 分派口:空槽/无选择空转(可用态口径与右键菜单 canPasteStyle 同源)\n const path = this.store.ui.selection\n return path !== null && this.pasteStyle(path)\n }\n case 'rename':\n return this.beginRename(this.store.ui.selection)\n case 'findReplace':\n // 查找替换(canvas-web-find-replace 工单 01):⌘F 分派 beginFind——\n // 面板开合语义内核只持会话态(Esc 关归 Vue 面板组件,工单 03 接线)\n return this.beginFind()\n case 'armCreateText':\n case 'armCreateTable':\n case 'armCreateQrCode':\n case 'armCreateImage':\n // 画拉建层武装四条目(canvas-web-drag-create 工单 03):T/G/Q/I 分派\n // 统一武装口——面板新增项与层型快捷键同缝(spec 决策 1);武装只置\n // ui 待命态,落库在画拉 endCreate(此处恒 true,状态栏提示归绑定层桥;\n // 动作→层型映射与绑定层共用 ARM_CREATE_LAYER_TYPES 单一事实源)\n this.armLayerCreate(ARM_CREATE_LAYER_TYPES[action])\n return true\n case 'toggleRulers':\n this.toggleRulers()\n return true\n case 'toggleLayerLock': {\n const path = this.selectableRootPath()\n if (path === null) return false\n this.toggleLayerLock(path)\n return true\n }\n case 'toggleLayerVisibility': {\n const path = this.selectableRootPath()\n if (path === null) return false\n this.toggleLayerVisibility(path)\n return true\n }\n case 'bringForward':\n return this.bringForward()\n case 'sendBackward':\n return this.sendBackward()\n case 'bringToFront':\n return this.bringToFront()\n case 'sendToBack':\n return this.sendToBack()\n case 'nudgeUp':\n return this.nudge(0, -NUDGE_STEP_PX)\n case 'nudgeDown':\n return this.nudge(0, NUDGE_STEP_PX)\n case 'nudgeLeft':\n return this.nudge(-NUDGE_STEP_PX, 0)\n case 'nudgeRight':\n return this.nudge(NUDGE_STEP_PX, 0)\n case 'nudgeUpCoarse':\n return this.nudge(0, -NUDGE_COARSE_STEP_PX)\n case 'nudgeDownCoarse':\n return this.nudge(0, NUDGE_COARSE_STEP_PX)\n case 'nudgeLeftCoarse':\n return this.nudge(-NUDGE_COARSE_STEP_PX, 0)\n case 'nudgeRightCoarse':\n return this.nudge(NUDGE_COARSE_STEP_PX, 0)\n case 'selectNextLayer':\n return this.selectNextLayer()\n case 'selectPrevLayer':\n return this.selectPrevLayer()\n case 'zoomReset':\n this.resetZoom()\n return true\n case 'fitToSurface':\n this.fitToSurface()\n return true\n case 'fitToSelection':\n this.fitToSelection()\n return true\n case 'helpShortcuts':\n // UI 面动作(kbd-nav 工单 04):绑定层桥拦截路由到帮助面板\n // (useShortcutsHelp),不会到达这里;case 仅为动作联合穷尽\n // (TS),防御性按不可用态口径返回 false\n return false\n case 'delete': {\n const path = this.store.ui.selection\n if (!path) return false\n this.deleteLayer(path)\n return true\n }\n }\n }\n\n // ---- 表格容器结构编辑(工单 12):重建路径与 graph 解码同一套 add 同步语义 ----\n\n /**\n * 新增表格行(面板「加行」入口):缺省行 + 缺省格(带文本内容)走重建路径\n * (行宽=表宽、内容宽=格宽、固定格压平内容高、行高取最高格——语义见\n * tableEditing.addTableRowInDraft),并自动选中新行;一次调用 = 一步历史。\n */\n addTableRow(tablePath: LayerPath): void {\n if (!this.store.doc) return\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n index.v = addTableRowInDraft(draft, tablePath) ?? -1\n })\n if (index.v >= 0) this.store.setSelection([...tablePath, 'rows', index.v])\n }\n\n /**\n * 新增表格单元格(面板「加格」入口):缺省格 + 缺省文本内容走重建路径\n * (内容宽=格宽、固定格压平内容高、行高取最高格),并自动选中新格;\n * 一次调用 = 一步历史。\n */\n addTableCell(rowPath: LayerPath): void {\n if (!this.store.doc) return\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n index.v = addTableCellInDraft(draft, rowPath) ?? -1\n })\n if (index.v >= 0) this.store.setSelection([...rowPath, 'cells', index.v])\n }\n\n /**\n * 新增模板表(「+」选择器入口,spec §2.1):createTemplateTable 缺省形态 +\n * 置顶 + 自动选中;一次调用 = 一步历史。rowsPath 必填(解码 rows_path_missing\n * 硬约束的对齐兜底,表单校验前置),空串 no-op。\n */\n addTemplateTable(rowsPath: string): void {\n if (!this.store.doc || rowsPath === '') return\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n index.v = insertRootLayerInDraft(draft, createTemplateTable({ rowsPath }) as Draft<Layer>)\n })\n if (index.v >= 0) this.store.setSelection(['layers', index.v])\n }\n\n /**\n * 模板行加格(spec §3.1):addTemplateCellInDraft 重建路径(末格宽/首格行宽、\n * 零高度耦合),自动选中新格;一次调用 = 一步历史。非模板态 no-op。\n */\n addTemplateCell(tablePath: LayerPath): void {\n if (!this.store.doc) return\n const index: TxOut<number> = { v: -1 }\n this.store.transact((draft) => {\n index.v = addTemplateCellInDraft(draft, tablePath) ?? -1\n })\n if (index.v >= 0) this.store.setSelection([...tablePath, 'template', 'cells', index.v])\n }\n\n /**\n * V1→V2 转换(spec §2.3):rowsPath 必填(空串 no-op,表单校验前置);rows\n * 子树内的选择/悬停回落表路径(rows 已清空不悬空)。一次调用 = 一步历史。\n */\n convertTableToTemplate(tablePath: LayerPath, rowsPath: string): void {\n if (!this.store.doc || rowsPath === '') return\n const done: TxOut<boolean> = { v: false }\n this.store.transact((draft) => {\n done.v = convertTableToTemplateInDraft(draft, tablePath, rowsPath) === true\n })\n if (done.v) this.clampSubtreeSelection(tablePath)\n }\n\n /**\n * V2→V1 逆向转换(spec §2.3):模板实例化为单行(V1 归一重断言——auto 格\n * 采纳/固化、行高取最高格);模板子树内的选择/悬停回落表路径。一次调用 =\n * 一步历史。\n */\n convertTableToRows(tablePath: LayerPath): void {\n if (!this.store.doc) return\n const done: TxOut<boolean> = { v: false }\n this.store.transact((draft) => {\n done.v = convertTableToRowsInDraft(draft, tablePath, this.textPolicies) === true\n })\n if (done.v) this.clampSubtreeSelection(tablePath)\n }\n\n /** 子树内选择/悬停回落到子树根(容器清空后不悬空);子树外原样不动 */\n private clampSubtreeSelection(rootPath: LayerPath): void {\n const selection = this.store.ui.selection\n if (selection !== null && selection.length > rootPath.length && pathStartsWith(selection, rootPath)) {\n this.store.setSelection(rootPath)\n }\n const hovered = this.store.ui.hovered\n if (hovered !== null && hovered.length > rootPath.length && pathStartsWith(hovered, rootPath)) {\n this.store.setHovered(rootPath)\n }\n }\n\n /**\n * 单元格移动:落点为「insert-before 原始序号」的目标行 + 序号。同行退化为\n * 数组序重排(与行重排同款 moveGuard 语义),跨行走重建路径(目标行高取\n * 最高格、内容层归属随格重同步)。一次调用 = 一步历史。\n */\n moveTableCellToRow(cellPath: LayerPath, targetRowPath: LayerPath, toCell: number): void {\n if (!this.store.doc) return\n const sourceRowPath = cellPath.slice(0, -2)\n if (pathsEqual(sourceRowPath, targetRowPath)) {\n const from = cellPath[cellPath.length - 1]\n if (typeof from === 'number') this.moveTableCell(sourceRowPath, from, toCell)\n return\n }\n const moved: TxOut<MovedSubtreeRef | null> = { v: null }\n this.store.transact((draft) => {\n moved.v = moveTableCellToRowInDraft(draft, cellPath, targetRowPath, toCell, this.textPolicies)\n })\n if (moved.v !== null) this.remapMovedSubtree(moved.v)\n }\n\n /** 同行单元格重排(直接改 cells 数组序,容器内嵌套数组序语义) */\n moveTableCell(rowPath: LayerPath, fromCell: number, toCell: number): void {\n if (!this.store.doc) return\n const moved: TxOut<{ from: number; to: number } | null> = { v: null }\n this.store.transact((draft) => {\n moved.v = moveTableCellInDraft(draft, rowPath, fromCell, toCell)\n })\n if (moved.v === null) return\n this.remapPathSlices(rowPath, 'cells', moved.v.from, moved.v.to)\n }\n\n /**\n * 行移动:落点为「insert-before 原始序号」的目标表 + 序号。同表退化为数组序\n * 重排(moveTableRow),跨表走重建路径(行宽同步目标表宽——addRow 副作用)。\n * 一次调用 = 一步历史。\n */\n moveTableRowToTable(rowPath: LayerPath, targetTablePath: LayerPath, toRow: number): void {\n if (!this.store.doc) return\n const sourceTablePath = rowPath.slice(0, -2)\n if (pathsEqual(sourceTablePath, targetTablePath)) {\n const from = rowPath[rowPath.length - 1]\n if (typeof from === 'number') this.moveTableRow(sourceTablePath, from, toRow)\n return\n }\n const moved: TxOut<MovedSubtreeRef | null> = { v: null }\n this.store.transact((draft) => {\n moved.v = moveTableRowToTableInDraft(draft, rowPath, targetTablePath, toRow)\n })\n if (moved.v !== null) this.remapMovedSubtree(moved.v)\n }\n\n /**\n * 格 autoHeight 切换:开且带内容 = 采纳内容动态高(autowrap 文本即\n * 「行数×行高+padding」)并固化(解码对带内容的格恒归一为固定高);开且空格 =\n * 置标志;关 = 清标志。采纳可能增高,行高随之取最高格。一次调用 = 一步历史。\n */\n setCellAutoHeight(cellPath: LayerPath, on: boolean): void {\n if (!this.store.doc) return\n const applied: TxOut<boolean> = { v: false }\n this.store.transact((draft) => {\n applied.v = setCellAutoHeightInDraft(draft, cellPath, on, this.textPolicies)\n })\n if (applied.v) this.pruneDanglingPaths()\n }\n\n // ---- 撤销/重做(工单 08):双栈语义全在 store,会话只透传查询与动作 ----\n\n get canUndo(): boolean {\n return this.store.canUndo\n }\n\n get canRedo(): boolean {\n return this.store.canRedo\n }\n\n undo(): void {\n this.store.undo()\n // 历史回放不携带 ui 路径态:撤销后选择/悬停可能指向已消失的图层(如撤销\n // 新增行),按「路径可解析」清理悬空态(工单 12 选择协同)\n this.pruneDanglingPaths()\n }\n\n redo(): void {\n this.store.redo()\n this.pruneDanglingPaths()\n }\n\n // ---- 订阅与失效 ----\n\n subscribe(listener: (change: EditorChange) => void): () => void {\n return this.store.subscribe(listener)\n }\n\n /** 外部脏源入口(如资源物化完成 → 'content') */\n invalidate(target: InvalidateTarget = 'both'): void {\n if (target === 'content') this.dirty.content = true\n else if (target === 'overlay') this.dirty.overlay = true\n else {\n this.dirty.content = true\n this.dirty.overlay = true\n }\n this.requestFrame()\n }\n\n /**\n * 挂起帧同步排空(canvas-web-render-perf 工单 01):绑定层在「物理缓冲已被清空、\n * 需要背靠背补绘」的时机调用(CanvasSurface 的 resizeBuffers 赋 canvas.width 清屏\n * 后——ResizeObserver 回调在本帧 rAF 之后,等下帧重绘必现一帧空白)。有挂起帧即\n * 取消排定并按当前脏标立即 flush;无挂起帧空转幂等。内核保持无 DOM:只操作既有\n * cancelFrame/flush,不感知帧源(rAF 语义归绑定层注入的调度器)。\n */\n flushPendingFrames(): void {\n if (!this.frameQueued) return\n // 先摘排定再同步 flush(被取消的帧回调不得在排空后再次触发);标志复位\n // 归 flush(),此处不重置\n this.cancelFrame?.()\n this.flush()\n }\n\n /** 取消挂起帧、退订 store、解绑绘制面 */\n dispose(): void {\n if (this.cancelFrame) this.cancelFrame()\n this.cancelFrame = null\n this.frameQueued = false\n this.unsubscribeStore()\n this.detachSurfaces()\n }\n\n // ---- 内部:合帧与重绘 ----\n\n /**\n * 结构变更后重映射 ui 分支的路径态(选择/悬停/重命名会话/锁定集合):路径是\n * 数组下标身份,容器 splice 后按 remapPathAfterSplice 平移,被移出子树的路径\n * 落地为清除。ui 整体替换、不进历史;值未变的路径 set* 内部值等短路。\n * 锁定集合(canvas-web-layer-lock 工单 01)作为集合平移——锁「胶在层上」走过\n * splice(兄弟增删/根层重排后锁仍指原层),被移出容器的锁定路径落地解锁。\n */\n private remapPathSlices(\n containerPath: LayerPath,\n key: 'layers' | 'rows' | 'cells',\n from: number,\n to: number,\n ): void {\n const remap = (path: LayerPath | null): LayerPath | null =>\n path === null ? null : remapPathAfterSplice(path, containerPath, key, from, to)\n const selection = remap(this.store.ui.selection)\n if (selection !== this.store.ui.selection) this.store.setSelection(selection)\n const hovered = remap(this.store.ui.hovered)\n if (hovered !== this.store.ui.hovered) this.store.setHovered(hovered)\n const renaming = remap(this.store.ui.renaming)\n if (renaming !== this.store.ui.renaming) this.store.setRenaming(renaming)\n this.store.setLockedPaths(this.remappedLocks(this.store.ui.lockedPaths, remap))\n }\n\n /**\n * 锁定集合经映射平移(canvas-web-layer-lock 工单 01):逐路径过 map、滤除\n * 落地 null 的悬空路径;集合未变时 setLockedPaths 内容等短路(无锁/未变\n * 不通知)。remapPathSlices 的 splice 平移与 remapMovedSubtree 的前缀重挂\n * 共用同一形状——后者必须传移动前捕获的集合(源 splice 步已丢掉移动子树\n * 内的锁)。\n */\n private remappedLocks(\n paths: readonly LayerPath[],\n map: (path: LayerPath) => LayerPath | null,\n ): LayerPath[] {\n return paths.reduce<LayerPath[]>((kept, locked) => {\n const mapped = map(locked)\n if (mapped !== null) kept.push(mapped)\n return kept\n }, [])\n }\n\n /**\n * 跨容器移动后的选择/悬停/锁定集合重映射(工单 12 + canvas-web-layer-lock\n * 工单 01):先做源容器纯删除与目标容器纯插入的索引平移(移动子树内的路径\n * 在源删除步被置 null,不参与平移),再把移动前落在移动子树内的路径按前缀\n * 重挂到新位置(子树内部相对结构不变,仅容器段整体替换)。锁定集合的重挂\n * 当前是不变量兜底——锁定恒为根层路径、跨容器移动的子树恒为行/格子树,两集\n * 天然不相交;将来锁面扩展或根层跨容器移动接入时语义已在。\n */\n private remapMovedSubtree(moved: MovedSubtreeRef): void {\n const selectionBefore = this.store.ui.selection\n const hoveredBefore = this.store.ui.hovered\n const lockedBefore = this.store.ui.lockedPaths\n this.remapPathSlices(moved.source.containerPath, moved.source.key, moved.source.from, moved.source.to)\n this.remapPathSlices(moved.target.containerPath, moved.target.key, moved.target.from, moved.target.to)\n const rebase = (before: LayerPath | null): LayerPath | null => {\n if (before === null || !pathStartsWith(before, moved.fromPath)) return null\n return [...moved.toPath, ...before.slice(moved.fromPath.length)] as LayerPath\n }\n const selection = rebase(selectionBefore)\n if (selection !== null && !pathsEqual(this.store.ui.selection, selection)) {\n this.store.setSelection(selection)\n }\n const hovered = rebase(hoveredBefore)\n if (hovered !== null && !pathsEqual(this.store.ui.hovered, hovered)) {\n this.store.setHovered(hovered)\n }\n const rebasedLocks = this.remappedLocks(lockedBefore, rebase)\n if (rebasedLocks.length > 0) {\n this.store.setLockedPaths([...this.store.ui.lockedPaths, ...rebasedLocks])\n }\n }\n\n /**\n * 悬空路径清理:选择/悬停/重命名会话指向已不存在的图层即清空(撤销/重做与\n * 采纳类收口共用)。锁定集合同门(canvas-web-layer-lock 工单 01):锁定路径\n * 悬空即解锁。已知限制(spec §3 记档不修):undo/redo 只有 prune 无平移——\n * 跨结构步撤销后锁可能错位(路径仍可解析、指到别的层)或清失,锁定钮常显\n * 可目视纠正;wire 持久化(锁长在层对象上)根治。\n */\n private pruneDanglingPaths(): void {\n const doc = this.store.doc\n if (!doc) return\n const { selection, hovered, renaming } = this.store.ui\n if (selection !== null && resolveLayer(doc, selection) === null) this.store.setSelection(null)\n if (hovered !== null && resolveLayer(doc, hovered) === null) this.store.setHovered(null)\n if (renaming !== null && resolveLayer(doc, renaming) === null) this.store.setRenaming(null)\n this.store.setLockedPaths(\n this.store.ui.lockedPaths.filter((locked) => resolveLayer(doc, locked) !== null),\n )\n }\n\n private onStoreChange(change: EditorChange): void {\n if (change.scope === 'doc') this.invalidate('both')\n else if (change.branch === 'viewport') this.invalidate('both')\n // 编辑会话开始/结束切换内容层的文本跳绘(textarea 接管该层呈现),双层都要重绘\n else if (change.branch === 'editing') this.invalidate('both')\n // 资源物化状态(placeholder-padding-hint 工单 02):装载落定伴随内容像素\n // 变化(完成出图/失败占位),提示面在绑定层读 ui 切片——双层重绘\n else if (change.branch === 'resourceStatuses') this.invalidate('both')\n // schema 声明不触达像素(候选消费在绑定层补全面),不参与重绘脏标\n else if (change.branch === 'dataSourceSchema') return\n // 选择/悬停/拖动会话/重命名/锁定集合/查找会话(命中高亮随 query/游标变)\n // 只影响 gizmo(锁定不改渲染产物——命中面是事件侧语义;拖动中的图层位移\n // 走 doc 分支另触发双层)\n else this.invalidate('overlay')\n }\n\n private requestFrame(): void {\n if (this.frameQueued) return\n this.frameQueued = true\n this.cancelFrame = this.scheduleFrame(() => this.flush())\n }\n\n private flush(): void {\n this.frameQueued = false\n this.cancelFrame = null\n const { content, overlay } = this.dirty\n this.dirty = { content: false, overlay: false }\n if (content) this.repaintContent()\n if (overlay) this.repaintOverlay()\n }\n\n private presentedViewport(): Viewport {\n return snapViewportToPhysicalPixels(this.store.ui.viewport, this.dpr)\n }\n\n private repaintContent(): void {\n const doc = this.store.doc\n const view = this.previewCanvas\n if (!doc || !view || !this.backend) return\n const viewport = this.presentedViewport()\n const transform: PreviewViewportTransform = {\n dpr: this.dpr,\n zoom: viewport.zoom,\n x: viewport.x,\n y: viewport.y,\n }\n // 视口变换是 Canvas2D 后端的可选能力;后端契约本身保持五原语不变\n ;(this.backend as Partial<ViewportAwareBackend>).setViewportTransform?.(transform)\n // 编辑中的文本层跳绘内容(textarea 接管该层文字呈现,防两侧断行叠加重影);\n // 盒(背景/边框)照常绘制,进出编辑视觉连续。跳绘判定按预览视图中的图层\n // 引用:模板子树在视图里是新实例,经路径映射取视图侧对象才能命中同一引用\n // (非模板子树结构共享,两棵树同引用)。\n const editing = this.store.ui.editing\n let editingLayer: Layer | null = null\n if (editing) {\n const viewPath = docPathToViewPath(doc, editing.path)\n editingLayer = viewPath ? resolveLayer(view, viewPath as LayerPath) : null\n }\n renderCanvas(view, this.backend, this.textPolicies, editingLayer\n ? { skipContent: (layer) => layer === editingLayer }\n : undefined)\n }\n\n private repaintOverlay(): void {\n if (!this.overlayPainter) return\n this.overlayPainter({\n viewport: this.presentedViewport(),\n dpr: this.dpr,\n doc: this.store.doc,\n })\n }\n}\n","/**\n * 相机纯函数(工单 05)。统一坐标定义(impl 研究文档 §2.6):\n *\n * screen = (scene - cam) * zoom\n *\n - 场景坐标全程保持画布整数像素语义(与后端整型口径一致),换算只发生在\n * 输入端(指针 → 场景)与呈现端(ctx transform);本模块只做数值换算,\n * 不触及任何 DOM/渲染对象。\n * - 相机平移可对齐物理像素(snapViewportToPhysicalPixels),避免缩放/平移\n * 时的亚像素抖动;zoom 数值本身不取整,缩放百分比显示用原值。\n */\n\n/** 视口:相机(场景坐标下的视口左上角)+ 缩放倍率 */\nexport interface Viewport {\n x: number\n y: number\n zoom: number\n}\n\nexport interface Point {\n x: number\n y: number\n}\n\nexport interface Size {\n width: number\n height: number\n}\n\n/** 场景矩形(适应选区的目标盒等) */\nexport interface Rect {\n x: number\n y: number\n width: number\n height: number\n}\n\n/** 缩放范围(可配置;缺省 5%–800%,工单 05 拍板) */\nexport interface ZoomBounds {\n min: number\n max: number\n}\n\nexport const DEFAULT_ZOOM_BOUNDS: ZoomBounds = { min: 0.05, max: 8 }\n\n/** 场景 → 屏幕(呈现端换算) */\nexport function sceneToScreen(viewport: Viewport, sceneX: number, sceneY: number): Point {\n return {\n x: (sceneX - viewport.x) * viewport.zoom,\n y: (sceneY - viewport.y) * viewport.zoom,\n }\n}\n\n/** 屏幕 → 场景(输入端换算:命中测试等) */\nexport function screenToScene(viewport: Viewport, screenX: number, screenY: number): Point {\n return {\n x: screenX / viewport.zoom + viewport.x,\n y: screenY / viewport.zoom + viewport.y,\n }\n}\n\n/** 缩放倍率钳位到范围 */\nexport function clampZoom(zoom: number, bounds: ZoomBounds): number {\n return Math.min(bounds.max, Math.max(bounds.min, zoom))\n}\n\n/**\n * 平移:屏幕位移按 zoom 折算回场景位移(cam -= delta / zoom),\n * 保证平移速度与缩放倍率无关。\n */\nexport function panBy(viewport: Viewport, deltaScreenX: number, deltaScreenY: number): Viewport {\n return {\n x: viewport.x - deltaScreenX / viewport.zoom,\n y: viewport.y - deltaScreenY / viewport.zoom,\n zoom: viewport.zoom,\n }\n}\n\n/**\n * 缩放至指针(zoom-to-pointer):保持指针下的场景点不动——\n * scene₀ = screen₀ / zoom + cam ⇒ cam' = scene₀ - screen₀ / zoom'\n * 目标倍率先钳位到范围,锚点关系按钳位后的倍率成立。\n */\nexport function zoomAtPoint(\n viewport: Viewport,\n screenX: number,\n screenY: number,\n nextZoom: number,\n bounds: ZoomBounds,\n): Viewport {\n const zoom = clampZoom(nextZoom, bounds)\n if (zoom === viewport.zoom) return viewport\n return {\n x: screenX / viewport.zoom + viewport.x - screenX / zoom,\n y: screenY / viewport.zoom + viewport.y - screenY / zoom,\n zoom,\n }\n}\n\n/**\n * 适应画布:整页可见(宽高取更紧一侧)、画布中心对齐视口中心。\n * margin 为可视区域内缩(屏幕像素)。缩放范围只钳**上界**(防小画布被荒谬放大),\n * 不钳下界——「适应」的语义是整页可见,超大画布允许低于交互缩放下界。\n * 零尺寸输入防御回落恒等视口。\n */\nexport function fitViewport(\n canvas: Size,\n surface: Size,\n bounds: ZoomBounds,\n margin: number,\n): Viewport {\n return fitRect({ x: 0, y: 0, width: canvas.width, height: canvas.height }, surface, bounds, margin)\n}\n\n/** 适应任意场景矩形(工单 06 适应选区):矩形整块可见、矩形中心对齐视口中心 */\nexport function fitRect(\n rect: Rect,\n surface: Size,\n bounds: ZoomBounds,\n margin: number,\n): Viewport {\n const innerW = surface.width - margin * 2\n const innerH = surface.height - margin * 2\n if (rect.width <= 0 || rect.height <= 0 || innerW <= 0 || innerH <= 0) {\n return { x: 0, y: 0, zoom: 1 }\n }\n const zoom = Math.min(bounds.max, Math.min(innerW / rect.width, innerH / rect.height))\n return {\n // 矩形中心 (rect.cx, rect.cy) 落在视口中心 (surface/2):cam = 中心 - 视口中心 / zoom\n x: rect.x + rect.width / 2 - surface.width / 2 / zoom,\n y: rect.y + rect.height / 2 - surface.height / 2 / zoom,\n zoom,\n }\n}\n\n/**\n * 保 zoom 平移入视的小边距(屏幕 px,canvas-web-find-replace 工单 02 定死):\n * 出视入视后盒缘与视口缘的留白。\n */\nexport const PAN_TO_BOX_MARGIN_PX = 12\n\n/**\n * 保 zoom 平移入视(canvas-web-find-replace 工单 02,spec 决策 6):查找导航的\n * 视口跟随语义,与 fitRect 的变焦语义分立——缩放倍率恒不变,只在目标盒出视时\n * 最小平移使整盒入视。\n *\n * - 盒完全在视口内(贴边也算)→ 原视口原样返回(引用相等,调用方可据此短路通知);\n * - 出视才平移,两轴独立求位,入视带含 margin 留白(屏幕 px):\n * - 盒装得下「视口 − 2×margin」:cam 钳进可行区间 = 各轴最小平移(非居中);\n * - 盒装得下视口但 margin 两侧放不下:margin 让位,钳进裸视口可行区间 = 贴缘\n * 最小平移(整盒可见优先于边距,spec「最小平移」字面口径);\n * - 盒大于视口:对齐盒左上(左上角落在 margin 处,右/下侧自然截断——工单\n * 定死取「阅读起点可见」而非居中)。\n * 零尺寸表面/非正 zoom 防御回落原视口。\n */\nexport function panToBox(viewport: Viewport, box: Rect, surface: Size, margin: number): Viewport {\n if (surface.width <= 0 || surface.height <= 0 || !(viewport.zoom > 0)) return viewport\n const panAxis = (boxPos: number, boxSize: number, surfaceSize: number, cam: number): number => {\n const left = (boxPos - cam) * viewport.zoom\n const right = left + boxSize * viewport.zoom\n if (left >= 0 && right <= surfaceSize) return cam\n const slack = surfaceSize - boxSize * viewport.zoom\n if (slack >= margin * 2) {\n // 可行区间 [lo, hi]:cam 取区间内任意值盒都整在 [margin, surface − margin]\n // 入视带内;钳进最近端 = 最小平移\n const lo = boxPos + boxSize - (surfaceSize - margin) / viewport.zoom\n const hi = boxPos - margin / viewport.zoom\n return Math.min(hi, Math.max(lo, cam))\n }\n if (slack >= 0) {\n // margin 带放不下整盒(盒贴近视口尺寸):margin 让位,裸视口可行区间\n // 钳近端 = 贴缘最小平移,盒从出视一侧探入\n const lo = boxPos + boxSize - surfaceSize / viewport.zoom\n const hi = boxPos\n return Math.min(hi, Math.max(lo, cam))\n }\n return boxPos - margin / viewport.zoom\n }\n const x = panAxis(box.x, box.width, surface.width, viewport.x)\n const y = panAxis(box.y, box.height, surface.height, viewport.y)\n if (x === viewport.x && y === viewport.y) return viewport\n return { x, y, zoom: viewport.zoom }\n}\n\n/**\n * 相机平移对齐物理像素:cam = round(cam * zoom * dpr) / (zoom * dpr)。\n * 只动 x/y,zoom 保持原值(百分比显示用原值)。\n */\nexport function snapViewportToPhysicalPixels(viewport: Viewport, dpr: number): Viewport {\n if (!(dpr > 0)) return viewport\n const gridX = viewport.zoom * dpr\n return {\n x: Math.round(viewport.x * gridX) / gridX,\n y: Math.round(viewport.y * gridX) / gridX,\n zoom: viewport.zoom,\n }\n}\n\n/**\n * 滚轮/触控板捏合的下一档缩放(excalidraw App.wheel.ts 同式,加性曲线):\n * - 单档步长 ZOOM_STEP,delta 钳位到 ±ZOOM_STEP*100(防大 delta 过冲);\n * - >100% 区按 log10(zoom) 增幅(越放大步长越大);\n * - 增幅项按 min(1, |Δ|/20) 衰减,防触控板小位移微抖。\n * deltaY > 0(向下滚)= 缩小。结果钳位到范围。\n */\nexport const WHEEL_ZOOM_STEP = 0.1\n\nexport function nextZoomByWheel(zoom: number, deltaY: number, bounds: ZoomBounds): number {\n if (deltaY === 0) return clampZoom(zoom, bounds)\n const sign = Math.sign(deltaY)\n const absDelta = Math.abs(deltaY)\n const delta = Math.min(absDelta, WHEEL_ZOOM_STEP * 100) * sign\n\n let next = zoom - delta / 100\n next += Math.log10(Math.max(1, zoom)) * -sign * Math.min(1, absDelta / 20)\n return clampZoom(next, bounds)\n}\n","/**\n * 命中测试纯函数(工单 06,spec「渲染与交互」):\n * 从视觉最上层向下命中——图层数组尾(后画在上)→ 头线性遍历 + 矩形包含,\n * 表格递归下钻(格内容 → 格 → 行 → 表)。盒几何与渲染模板共用\n * (resolveLayerBox + resolveChildAt,与 walkLayer 同一套推进公式),\n * 负溢出(盒出画布)照常包含,不做空间索引(图元量级小)。\n */\nimport { resolveChildAt, resolveLayerBox, type Canvas, type Layer, type LayerBox, type TextLayoutPolicies } from '@hankchen/canvas'\n\nimport { isLockedPath, type LayerPath } from '../shared/layerPath'\n\n/** 半开区间包含 [x, x+w) × [y, y+h):相邻格边界恰好归一格,零尺寸不可命中 */\nfunction contains(box: LayerBox, x: number, y: number): boolean {\n return box.width > 0\n && box.height > 0\n && x >= box.x\n && x < box.x + box.width\n && y >= box.y\n && y < box.y + box.height\n}\n\n/** 命中选项:过滤集合由调用方注入(内核 ui 分支只读投影,纯函数不自取状态) */\nexport interface HitTestOptions {\n /**\n * 锁定根层路径集合(canvas-web-layer-lock 工单 01):锁定的根层整子树退出\n * 命中面、命中穿透下方层,与 visible 过滤同门;与底图豁免叠加时锁优先\n * (豁免只跳自身盒,锁整子树退出)。\n */\n readonly lockedPaths?: readonly LayerPath[]\n}\n\n/**\n * 全幅判定:底层自身盒 ⊇ 画布矩形(负溢出照算),与视口/缩放无关。\n * spec「渲染与交互」2026-09-27 拍板的底图豁免(Fabric backgroundImage 不入命中图、\n * Konva listening(false) 同构):只作用于数组头根层——「数组按 priority 降序、\n * 头先画垫底」的叠放不变量决定了它是唯一的视觉垫底层。\n */\nfunction coversCanvas(box: LayerBox, canvasWidth: number, canvasHeight: number): boolean {\n return box.x <= 0\n && box.y <= 0\n && box.x + box.width >= canvasWidth\n && box.y + box.height >= canvasHeight\n}\n\nfunction hitWalk(\n layer: Layer,\n path: LayerPath,\n box: LayerBox,\n x: number,\n y: number,\n policies?: TextLayoutPolicies,\n skipOwnBox = false,\n): LayerPath | null {\n // 子层后画在上:先下钻(尾→头),都未中再试自身盒(次序不动——豁免只跳自身盒)\n switch (layer.type) {\n case 'TableLayer':\n for (let i = layer.rows.length - 1; i >= 0; i -= 1) {\n const child = resolveChildAt(layer, box, 'rows', i, policies)\n if (!child) continue\n const hit = hitWalk(child.layer, [...path, 'rows', i], child.box, x, y, policies)\n if (hit) return hit\n }\n break\n case 'TableRowLayer':\n for (let i = layer.cells.length - 1; i >= 0; i -= 1) {\n const child = resolveChildAt(layer, box, 'cells', i, policies)\n if (!child) continue\n const hit = hitWalk(child.layer, [...path, 'cells', i], child.box, x, y, policies)\n if (hit) return hit\n }\n break\n case 'TableCellLayer':\n // 内容层与单元格同原点,最上优先\n if (layer.content) {\n const child = resolveChildAt(layer, box, 'content', 0, policies)\n if (child) {\n const hit = hitWalk(child.layer, [...path, 'content'], child.box, x, y, policies)\n if (hit) return hit\n }\n }\n break\n case 'ImageLayer':\n case 'TextLayer':\n case 'QrCodeLayer':\n case 'TableRowTemplate':\n // 行模板子树不在 rows/cells/content 内,命中面天然为空(工票 02 穷举补臂)\n break\n }\n\n return !skipOwnBox && contains(box, x, y) ? path : null\n}\n\n/**\n * 场景坐标命中:返回视觉最上被中图层的路径(未中返回 null)。\n * 隐藏根层(layer-panel-ux 工单 10)与锁定根层(canvas-web-layer-lock 工单 01)\n * 整子树退出命中面——渲染端整层跳过 / 锁定层只退交互面(渲染照常),不可见/\n * 被锁定即不可点选,命中穿透其下方的层。\n */\nexport function hitTest(\n canvas: Canvas,\n sceneX: number,\n sceneY: number,\n policies?: TextLayoutPolicies,\n options?: HitTestOptions,\n): LayerPath | null {\n const lockedPaths = options?.lockedPaths\n for (let i = canvas.layers.length - 1; i >= 0; i -= 1) {\n const layer = canvas.layers[i]!\n if (layer.visible === false) continue\n const rootPath: LayerPath = ['layers', i]\n if (lockedPaths !== undefined && isLockedPath(rootPath, lockedPaths)) continue\n const box = resolveLayerBox(layer, 0, 0, canvas.width, canvas.height, policies)\n // 全幅底层豁免只跳过自身盒测试(不能对整层 continue):全幅底表的行/格/格内容照常可命中\n const skipOwnBox = i === 0 && coversCanvas(box, canvas.width, canvas.height)\n const hit = hitWalk(layer, rootPath, box, sceneX, sceneY, policies, skipOwnBox)\n if (hit) return hit\n }\n return null\n}\n","/**\n * 选择身份 = 图层路径(工单 06)。\n *\n * graph 无 id 字段且往返恒等契约禁止私加(spec「内核(store 与历史)」),\n * 选中身份用「从画布根到图层的数组路径」表达,语法即 immer patch path 前缀:\n *\n * ['layers', i] 根层\n * ['layers', i, 'rows', j] 表行\n * ['layers', i, 'rows', j, 'cells', k] 单元格\n * ['layers', i, 'rows', j, 'cells', k, 'content'] 格内容层\n * ['layers', i, 'template', 'cells', k] 行模板格(无下标的 template 段)\n * ['layers', i, 'template'] 行模板替身(表级,spec §2.2)\n *\n * 于是「对选中层改字段」= path 拼上字段段(如 + ['position', 'x'])直接得到\n * patch path;选择态住 ui 分支(不进历史),文档写入一律经路径导航。\n * 全模块纯函数、无 DOM。\n */\nimport { resolveChildAt, resolveLayerBox, type Canvas, type Layer, type LayerBox, type TextLayoutPolicies } from '@hankchen/canvas'\n\n/** 图层路径:patch path 前缀形态的只读数组(语法由 isLayerPath 校验) */\nexport type LayerPath = readonly (string | number)[]\n\n/** 索引段必须是安全非负整数 */\nfunction isIndexSegment(value: unknown): value is number {\n return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0\n}\n\n/** 结构校验(不触文档):只回答「像不像一条图层路径」;越界由 resolveLayer 判定 */\nexport function isLayerPath(value: unknown): value is LayerPath {\n if (!Array.isArray(value) || value.length < 2) return false\n if (value[0] !== 'layers' || !isIndexSegment(value[1])) return false\n\n // 下降序列固定:rows → cells → content(可省前段,不可乱序/重复)。行模板\n // 子树以无下标的 'template' 段替代 rows 段(spec §2.2):template 可后接\n // cells 下钻(预览行选中),也可作路径收尾——收尾形态是**行模板替身路径**\n // (大纲模板子树选中/容器解析用),不是普通选择身份。\n const descent = ['rows', 'cells', 'content']\n let stage = 0\n let i = 2\n while (i < value.length) {\n const key = value[i]\n if (stage === 0 && key === 'template') {\n if (i === value.length - 1) return true\n if (value[i + 1] !== 'cells') return false\n stage = 1\n i += 1\n continue\n }\n const expected = descent[stage]\n if (key !== expected) return false\n if (expected === 'content') return i === value.length - 1\n if (!isIndexSegment(value[i + 1])) return false\n stage += 1\n i += 2\n }\n return true\n}\n\n/** 路径是否落在行模板子树内(含 'template' 段;选中/属性写入的模板态分流用) */\nexport function isTemplateSubtreePath(path: LayerPath): boolean {\n return path.includes('template')\n}\n\n/**\n * 根层路径判定(工单 14):置顶/置底等 priority 语义只作用于根层(容器内行/格\n * 是数组序语义,priority 不参与排序)——右键菜单与快捷键入口据此裁剪动作可用态。\n */\nexport function isRootLayerPath(path: LayerPath): boolean {\n return isLayerPath(path) && path.length === 2\n}\n\n/** 路径值相等(选择态变更短路依赖它;引用比较对每次新造的数组无效) */\nexport function pathsEqual(a: LayerPath | null, b: LayerPath | null): boolean {\n if (a === b) return true\n if (!a || !b || a.length !== b.length) return false\n for (let i = 0; i < a.length; i += 1) {\n if (a[i] !== b[i]) return false\n }\n return true\n}\n\n/**\n * 前缀判定:path 落在 prefix 子树内(含相等)。跨容器移动后重挂选择路径用\n * (工单 12)——子树整体换容器时,移动前捕获的路径按前缀重挂到新位置。\n */\nexport function pathStartsWith(path: LayerPath, prefix: LayerPath): boolean {\n if (path.length < prefix.length) return false\n for (let i = 0; i < prefix.length; i += 1) {\n if (path[i] !== prefix[i]) return false\n }\n return true\n}\n\n/**\n * 路径是否处于锁定子树(canvas-web-layer-lock 工单 01):lockedPaths 中存在其\n * 前缀路径即锁定(锁定恒为根层路径,即根祖先被锁)。谓词收口一处——hitTest\n * 过滤、动作 guard(beginDrag/deleteLayer)、gizmo、面板投影共用,防各处手写\n * 路径比对;语义与 spec §2 同源:锁根层即整子树受保护。\n */\nexport function isLockedPath(path: LayerPath, lockedPaths: readonly LayerPath[]): boolean {\n return lockedPaths.some((locked) => pathStartsWith(path, locked))\n}\n\n/** 沿路径导航(根可为 immer draft:同一套下降逻辑同时服务只读解析与事务写入) */\nfunction navigate(root: unknown, path: LayerPath): unknown {\n // (属性名, 索引) 成对推进;'template' 与尾段 'content' 无索引,落到节点本身\n let current: unknown = root\n let i = 0\n while (i < path.length) {\n const container = current as Record<string, unknown> | null\n if (container === null || typeof container !== 'object') return null\n const key = path[i]\n if (typeof key !== 'string') return null\n current = container[key]\n if (current === null || current === undefined) return null\n if (key === 'template') {\n i += 1\n continue\n }\n if (i + 1 >= path.length) break\n const list = current as readonly unknown[]\n const index = path[i + 1]\n if (!Array.isArray(list) || typeof index !== 'number') return null\n current = list[index]\n if (current === null || current === undefined) return null\n i += 2\n }\n return current\n}\n\n/** 对文档解析路径:越界/形态与文档不符返回 null(doc 侧校验) */\nexport function resolveLayer(doc: Canvas, path: LayerPath): Layer | null {\n if (!isLayerPath(path)) return null\n const layer = navigate(doc, path)\n if (layer === null || typeof layer !== 'object' || !('type' in layer)) return null\n return layer as Layer\n}\n\n/**\n * 路径所属根层(layer-panel-ux 工单 10):LayerBase 面字段(visible 等)只作用\n * 根层,任意深度的路径第 2 段即根层下标——gizmo 隐藏过滤与右键菜单显隐标签等\n * 绑定层读取位共用,收口「path[1] 是根层」的路径结构知识。越界返回 null。\n */\nexport function rootLayerOf(doc: Canvas, path: LayerPath): Layer | null {\n const index = path[1]\n if (typeof index !== 'number' || !Number.isSafeInteger(index) || index < 0) return null\n return doc.layers[index] ?? null\n}\n\n/**\n * 级联归属链(结构纯函数,无需文档):格内容 → 格 → 行 → 表;根层无父级。\n * Escape 升级沿此链逐级取父,链尽即清空选择。模板子树链(spec §2.2):模板格\n * 内容 → 模板格 → 行模板(替身路径,可选中)→ 表——行模板是链上正式一级。\n */\nexport function selectionParentPath(path: LayerPath): LayerPath | null {\n if (path.length < 2) return null\n if (path[path.length - 1] === 'content') return path.slice(0, -1)\n // 行模板替身收尾:template 段无下标容器对,父级 = 表\n if (path[path.length - 1] === 'template') return path.slice(0, -1)\n if (path.length > 2) {\n // 模板格的父级 = 行模板替身路径(保留 template 段,不再跳过)\n return path.slice(0, -2)\n }\n return null\n}\n\n/**\n * 路径处图层的绝对盒(下钻几何经 canvas 的 resolveChildAt,与渲染模板\n * walkLayer 同一套推进公式:行纵向累加、格横向累加、格内容与格同原点)。\n * gizmo 选择框、命中测试、适应选区共用,保证与绘制不漂移。\n */\nexport function layerBoxByPath(\n doc: Canvas,\n path: LayerPath,\n policies?: TextLayoutPolicies,\n): LayerBox | null {\n if (!isLayerPath(path)) return null\n\n const rootIndex = path[1] as number\n const root = doc.layers[rootIndex]\n if (root === undefined) return null\n let layer: Layer = root\n let box = resolveLayerBox(root, 0, 0, doc.width, doc.height, policies)\n\n // 逐段下钻:每段经共享几何解析目标孩子(越界/形态不符返回 null);\n // 'template' 段无下标,盒与首行同位(resolveChildAt 同一推进公式)\n let i = 2\n while (i < path.length) {\n const key = path[i] as 'rows' | 'cells' | 'content' | 'template'\n if (key === 'template') {\n const child = resolveChildAt(layer, box, 'template', 0, policies)\n if (!child) return null\n layer = child.layer\n box = child.box\n i += 1\n continue\n }\n const index = path[i + 1] as number\n const child = resolveChildAt(layer, box, key, index, policies)\n if (!child) return null\n layer = child.layer\n box = child.box\n i += 2\n }\n return box\n}\n\n/**\n * 结构变更后的路径重映射(纯函数,工单 10):containerPath 容器的 key 子列表发生\n * 「摘除 from 处 1 项、插回 to 处」(to = 插入后的最终下标;to === from 即纯删除)。\n * 前缀匹配容器外的路径原样返回;被移出容器的子树返回 null(调用方落地为清除选择)。\n */\nexport function remapPathAfterSplice(\n path: LayerPath,\n containerPath: LayerPath,\n key: 'layers' | 'rows' | 'cells',\n from: number,\n to: number,\n): LayerPath | null {\n if (path.length < containerPath.length + 2) return path\n for (let i = 0; i < containerPath.length; i += 1) {\n if (path[i] !== containerPath[i]) return path\n }\n if (path[containerPath.length] !== key) return path\n const index = path[containerPath.length + 1]\n if (typeof index !== 'number') return path\n const rest = path.slice(containerPath.length + 2)\n const head: LayerPath = [...containerPath, key]\n\n if (from === to) {\n if (index === from) return null\n return index > from ? [...head, index - 1, ...rest] : path\n }\n if (index === from) return [...head, to, ...rest]\n if (from < index && index <= to) return [...head, index - 1, ...rest]\n if (to <= index && index < from) return [...head, index + 1, ...rest]\n return path\n}\n","/**\n * 吸附数学 + 参考线轴几何(ruler-guides-snap 工单 01,ADR 0012;工单 07 起缩放\n * 手势共用同一条求位缝——拖动九点与缩放移动缘是 resolveSnapPoints 的两个特例)。\n *\n * 吸附(snap)语义(术语见根 CONTEXT.md):拖动图层时其盒缘与中心(九点)自动\n * 对齐到吸附源的编辑器行为——源为其他可见根层盒的缘与中心、画布水平/垂直中轴、\n * 参考线轴;画布四边不设轴(精确贴边归第一批对齐画布按钮,ADR 0011)。命中阈值\n * 按屏幕像素常量计(缩放无关,换算场景值后使用),多轴命中取最近,一次拖动至多\n * 吸一垂直轴 + 一水平轴。吸附是拖动过程的连续行为,与对齐画布的一次性动作分立。\n *\n * 参考线(guide)是使用者从标尺拖出的会话级对位轴:住内核 ui 分支——不进历史、\n * 不写 graph、保存零改动;本模块只定义其形态与「供轴」,增删动作在会话门面。\n * 全模块纯函数、无 DOM。\n */\nimport { resolveLayerBox, type Canvas, type LayerBox, type TextLayoutPolicies } from '@hankchen/canvas'\n\n/** 轴向:vertical = 竖直线(修正 x)、horizontal = 水平线(修正 y),场景坐标 */\nexport type GuideOrientation = 'vertical' | 'horizontal'\n\n/** 参考线:会话级对位轴(id 会话内自增,position 为场景坐标) */\nexport interface Guide {\n readonly id: number\n readonly orientation: GuideOrientation\n readonly position: number\n}\n\n/** 吸附轴来源:其他可见根层盒 / 画布中轴 / 参考线 */\nexport type SnapAxisSource = 'layer' | 'canvas-center' | 'guide'\n\n/** 吸附轴:orientation 为线的方向,position 为场景坐标(源与命中同形) */\nexport interface SnapAxis {\n readonly orientation: GuideOrientation\n readonly position: number\n readonly source: SnapAxisSource\n}\n\n/** 吸附求位的盒几何入参(LayerBox 的位置尺寸切片) */\nexport type SnapBoxGeometry = Pick<LayerBox, 'x' | 'y' | 'width' | 'height'>\n\n/** 吸附求位结果:两轴修正量 + 命中轴(至多一垂直 + 一水平,吸附线呈现消费) */\nexport interface SnapResolution {\n readonly dx: number\n readonly dy: number\n readonly axes: readonly SnapAxis[]\n}\n\n/** 拖动吸附屏幕阈值(css 像素,缩放无关;场景阈值 = 阈值 / zoom) */\nexport const SNAP_THRESHOLD_SCREEN_PX = 6\n\n/** 屏幕阈值 → 场景阈值(放大时场景窗口收紧,屏幕手感恒定) */\nexport function snapThresholdScene(zoom: number): number {\n return SNAP_THRESHOLD_SCREEN_PX / zoom\n}\n\n/**\n * 吸附层盒来源:可见根层盒(渲染端整层跳过、命中测试整子树退出——同一 visible\n * 过滤,hitTest 同款),并排除拖动层自身根(自缘不供轴,防自身 delta 0 命中噪声)。\n * excludeRootIndex 传 null = 排除面为空(copyMode 首移:副本未入库、源层供轴,\n * alt-drag-paste 工单 01)。\n */\nexport function visibleRootBoxes(\n doc: Canvas,\n excludeRootIndex: number | null,\n policies?: TextLayoutPolicies,\n): LayerBox[] {\n const boxes: LayerBox[] = []\n for (let i = 0; i < doc.layers.length; i += 1) {\n if (excludeRootIndex !== null && i === excludeRootIndex) continue\n const layer = doc.layers[i]!\n if (layer.visible === false) continue\n boxes.push(resolveLayerBox(layer, 0, 0, doc.width, doc.height, policies))\n }\n return boxes\n}\n\n/**\n * 吸附轴集合:根层盒左/中/右 + 上/中/下缘(source=layer)+ 画布水平/垂直中轴\n * (source=canvas-center,四边不设轴)+ 参考线轴(source=guide)。集合次序即\n * 多轴平局时的取先次序:层盒 → 画布中轴 → 参考线。\n */\nexport function snapAxesFromBoxes(\n canvasWidth: number,\n canvasHeight: number,\n boxes: readonly LayerBox[],\n guides: readonly Guide[],\n): SnapAxis[] {\n const axes: SnapAxis[] = []\n for (const box of boxes) {\n axes.push(\n { orientation: 'vertical', position: box.x, source: 'layer' },\n { orientation: 'vertical', position: box.x + box.width / 2, source: 'layer' },\n { orientation: 'vertical', position: box.x + box.width, source: 'layer' },\n { orientation: 'horizontal', position: box.y, source: 'layer' },\n { orientation: 'horizontal', position: box.y + box.height / 2, source: 'layer' },\n { orientation: 'horizontal', position: box.y + box.height, source: 'layer' },\n )\n }\n axes.push(\n { orientation: 'vertical', position: canvasWidth / 2, source: 'canvas-center' },\n { orientation: 'horizontal', position: canvasHeight / 2, source: 'canvas-center' },\n )\n for (const guide of guides) {\n axes.push({ orientation: guide.orientation, position: guide.position, source: 'guide' })\n }\n return axes\n}\n\n/**\n * 单轴求位:拖动盒在该轴向上的三点(左/中/右或上/中/下)对全部同向轴取\n * |delta| 最小且 ≤ 阈值的命中;无命中返回 null。平局取先遇到的轴(集合次序)。\n */\nfunction nearestAxisHit(\n points: readonly number[],\n axes: readonly SnapAxis[],\n orientation: GuideOrientation,\n threshold: number,\n): { delta: number; axis: SnapAxis } | null {\n let best: { delta: number; axis: SnapAxis } | null = null\n for (const axis of axes) {\n if (axis.orientation !== orientation) continue\n for (const point of points) {\n const delta = axis.position - point\n const distance = Math.abs(delta)\n if (distance > threshold) continue\n if (best === null || distance < Math.abs(best.delta)) best = { delta, axis }\n }\n }\n return best\n}\n\n/**\n * 按轴向分组的求位点集(resolveSnapPoints 入参):拖动九点与缩放移动缘共用\n * (工单 07——缩放只给移动缘位点,见 spatial/resize.resizeSnapPoints)。\n */\nexport interface SnapPoints {\n readonly vertical: readonly number[]\n readonly horizontal: readonly number[]\n}\n\n/**\n * 任意位点集吸附求位:垂直位点对垂直轴、水平位点对水平轴各取最近命中(已对齐\n * delta=0 也回显命中轴——吸附线呈现「已对齐」状态);每轴至多一条命中轴,两轴\n * 修正量由调用方并入本次几何写入(同一事务、同一历史步)。拖动盒九点吸附\n * (resolveSnap)是本函数位点集的特例,缩放移动缘吸附(工单 07)是另一特例。\n */\nexport function resolveSnapPoints(\n points: SnapPoints,\n axes: readonly SnapAxis[],\n threshold: number,\n): SnapResolution {\n const vertical = nearestAxisHit(points.vertical, axes, 'vertical', threshold)\n const horizontal = nearestAxisHit(points.horizontal, axes, 'horizontal', threshold)\n return {\n dx: vertical ? vertical.delta : 0,\n dy: horizontal ? horizontal.delta : 0,\n axes: [\n ...(vertical ? [vertical.axis] : []),\n ...(horizontal ? [horizontal.axis] : []),\n ],\n }\n}\n\n/**\n * 拖动盒九点吸附求位:盒左/中/右三点对垂直轴、上/中/下三点对水平轴各取最近\n * 命中(已对齐 delta=0 也回显命中轴——吸附线呈现「已对齐」状态);返回的修正量\n * 由调用方并入本次拖动的 position 写入(同一事务、同一历史步)。\n */\nexport function resolveSnap(\n box: SnapBoxGeometry,\n axes: readonly SnapAxis[],\n threshold: number,\n): SnapResolution {\n return resolveSnapPoints(\n {\n vertical: [box.x, box.x + box.width / 2, box.x + box.width],\n horizontal: [box.y, box.y + box.height / 2, box.y + box.height],\n },\n axes,\n threshold,\n )\n}\n","/**\n * 画拉建层橡皮筋求位几何(drag-create 工单 01,术语见根 CONTEXT.md「画拉建层」\n * 「武装」词条)。\n *\n * 拖拽期两点正规化出橡皮筋矩形(x = min、width = |dx|,y 同,反向拖不翻转);\n * QrCodeLayer 钳 height := width(高随宽从动保持方形,八柄缩放「QR 从动轴不\n * 采纳」同门);橡皮筋左右/上下缘各两点进 resolveSnapPoints——吸附数学零新增,\n * 命中修正整矩形平移并入几何(两点正规化与 QR 方形在修正后原样保持)。死区常量\n * 屏幕像素口径同 SNAP_THRESHOLD_SCREEN_PX(场景阈值 = 阈值 / zoom,缩放无关)。\n * 全模块纯函数、无 DOM;会话编排(武装/落库/解除)在会话门面。\n */\nimport type { LayerType } from '@hankchen/canvas'\n\nimport type { Point } from './camera'\nimport { resolveSnapPoints, type SnapAxis, type SnapBoxGeometry } from './snap'\n\n/** 画拉死区阈值(css 像素,缩放无关;场景阈值 = 阈值 / zoom):位移不越过即视作\n * 点击,endCreate 回落「缺省尺寸、左上角对准点击点」,不产生零尺寸层 */\nexport const CREATE_DEAD_ZONE_SCREEN_PX = 4\n\n/** 橡皮筋求位结果:并入吸附修正后的矩形 + 命中轴(至多一垂直 + 一水平) */\nexport interface CreateRectResult {\n readonly rect: SnapBoxGeometry\n readonly axes: readonly SnapAxis[]\n}\n\n/**\n * 画拉橡皮筋求位:两点正规化(起点场景坐标 + 当前点 → 左上角原点矩形)→\n * QR 钳 height := width → 左右/上下缘各两点对轴集合求位(已有已对齐 delta=0\n * 也回显命中轴的语义,dragTo/resizeTo 同门)→ 修正整矩形平移并入。钳方先于\n * 吸附——QR 的方边(y + width)才是真实的下缘吸附位点。\n */\nexport function createRubberBandRect(\n type: LayerType,\n start: Point,\n current: Point,\n axes: readonly SnapAxis[],\n threshold: number,\n): CreateRectResult {\n const x = Math.min(start.x, current.x)\n const y = Math.min(start.y, current.y)\n const width = Math.abs(current.x - start.x)\n const height = type === 'QrCodeLayer' ? width : Math.abs(current.y - start.y)\n const resolution = resolveSnapPoints(\n { vertical: [x, x + width], horizontal: [y, y + height] },\n axes,\n threshold,\n )\n return {\n rect: { x: x + resolution.dx, y: y + resolution.dy, width, height },\n axes: resolution.axes,\n }\n}\n","/**\n * 八柄缩放几何(工单 07)。\n *\n * 缩放语义(工单验收项 1/2,术语见根 CONTEXT.md「八柄缩放」词条):编辑者拖动\n * 选中框上的八柄(四角 + 四边中点)调整图层盒——对缘固定、移动缘随指针;\n * minSize 钳位保证不产生负宽高、不翻转(盒模型契约无通用 angle,明确不做\n * 旋转/翻转)。全部纯函数、无 DOM,缩放手势的文档写回在会话门面(editor.ts\n * resizeTo:position 反解 + auto 标志采纳 + 表格强同步)。\n *\n * 角色可缩放面与属性面板的字段权威过滤(editor-vue fieldSchema ROLE_HIDDEN_KEYS\n * 语义同构):解码/fromGraph 强同步的尺寸字段不可经柄改——行宽=表宽、格内容\n * 宽高=格尺寸;柄集合 = 轴可缩放面的柄投影,gizmo 渲染与手势起点共用同一条缝。\n */\nimport { type Canvas, type LayerBox } from '@hankchen/canvas'\n\nimport { resolveLayer, type LayerPath } from '../shared/layerPath'\nimport type { SnapPoints } from './snap'\n\n/** 八柄:四角 + 四边中点(命名 = 柄在盒上的方位) */\nexport const RESIZE_HANDLES = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'] as const\n\nexport type ResizeHandle = (typeof RESIZE_HANDLES)[number]\n\n/** 缩放最小边长(场景 px):钳位下限,杜绝负宽高与零尺寸不可见层 */\nexport const RESIZE_MIN_SIZE_PX = 1\n\n/** 缩放几何的盒入参(LayerBox 的位置尺寸切片,与吸附求位入参同形) */\nexport type ResizeBoxGeometry = Pick<LayerBox, 'x' | 'y' | 'width' | 'height'>\n\n/** 缩放求位的盒结果(绝对坐标,写回面消费) */\nexport interface ResizeBoxResult {\n readonly x: number\n readonly y: number\n readonly width: number\n readonly height: number\n}\n\n/** 柄的方向分类(单点维护:resizeBox/resizeSnapPoints/轴判定共读同一份) */\nconst WEST_HANDLES: ReadonlySet<ResizeHandle> = new Set(['nw', 'w', 'sw'])\nconst EAST_HANDLES: ReadonlySet<ResizeHandle> = new Set(['ne', 'e', 'se'])\nconst NORTH_HANDLES: ReadonlySet<ResizeHandle> = new Set(['nw', 'n', 'ne'])\nconst SOUTH_HANDLES: ReadonlySet<ResizeHandle> = new Set(['sw', 's', 'se'])\n\n/** 柄是否作用于横向(宽轴):东/西缘与四角 */\nexport function handleResizesWidth(handle: ResizeHandle): boolean {\n return WEST_HANDLES.has(handle) || EAST_HANDLES.has(handle)\n}\n\n/** 柄是否作用于纵向(高轴):南/北缘与四角 */\nexport function handleResizesHeight(handle: ResizeHandle): boolean {\n return NORTH_HANDLES.has(handle) || SOUTH_HANDLES.has(handle)\n}\n\n/**\n * 八柄缩放求位:对缘固定、移动缘 = 起始缘 + 位移,minSize 钳位(先钳尺寸再\n * 反推移动缘坐标——拖过对缘时停在 minSize 而不翻转,对缘坐标严格不动)。\n * 非本柄轴的位移分量忽略(n 不吃 dx、e 不吃 dy)。\n */\nexport function resizeBox(\n start: ResizeBoxGeometry,\n handle: ResizeHandle,\n dx: number,\n dy: number,\n minSize: number = RESIZE_MIN_SIZE_PX,\n): ResizeBoxResult {\n const west = WEST_HANDLES.has(handle)\n const north = NORTH_HANDLES.has(handle)\n const width = west\n ? start.width - dx\n : EAST_HANDLES.has(handle)\n ? start.width + dx\n : start.width\n const height = north\n ? start.height - dy\n : SOUTH_HANDLES.has(handle)\n ? start.height + dy\n : start.height\n const clampedWidth = Math.max(width, minSize)\n const clampedHeight = Math.max(height, minSize)\n return {\n x: west ? start.x + start.width - clampedWidth : start.x,\n y: north ? start.y + start.height - clampedHeight : start.y,\n width: clampedWidth,\n height: clampedHeight,\n }\n}\n\n/**\n * 本步位移下的移动缘场景坐标(钳位前——吸附修正施加在指针位移上,再进\n * resizeBox 钳位,吸附不破「对缘固定 + minSize」语义)。只收真正被写动的缘:\n * 角柄双缘、边柄单缘;固定缘与盒中心不参与缩放吸附(中心对齐是拖动期行为,\n * 缩放期跟随动缘即可)。\n */\nexport function resizeSnapPoints(\n start: ResizeBoxGeometry,\n handle: ResizeHandle,\n dx: number,\n dy: number,\n): SnapPoints {\n const vertical: number[] = []\n const horizontal: number[] = []\n if (WEST_HANDLES.has(handle)) vertical.push(start.x + dx)\n if (EAST_HANDLES.has(handle)) vertical.push(start.x + start.width + dx)\n if (NORTH_HANDLES.has(handle)) horizontal.push(start.y + dy)\n if (SOUTH_HANDLES.has(handle)) horizontal.push(start.y + start.height + dy)\n return { vertical, horizontal }\n}\n\n/** 可缩放轴(width = 横向宽字段可写、height = 纵向高字段可写) */\nexport interface ResizeAxes {\n readonly width: boolean\n readonly height: boolean\n}\n\n/** 双轴皆不可缩放(格内容/行模板替身/路径悬空的共同回退) */\nconst NO_AXES: ResizeAxes = { width: false, height: false }\n\n/**\n * 路径处图层的可缩放轴(角色权威过滤,与 editor-vue fieldSchema\n * ROLE_HIDDEN_KEYS 逐角色同构——面板隐藏的尺寸字段即柄不可写的轴):\n * - root:双轴(根表宽写经 canonicalize 联动行宽,面板同门放行);\n * - row:仅纵向(行宽 = 表宽强同步,面板同门隐藏 width);\n * - cell:双轴(内容宽/行高随 canonicalize 强同步跟随,面板同门放行);\n * - content / templateContent:content 双向强同步全隐藏;模板格内容宽度耦合\n * 沿用、高度豁免放行(ADR 0006 同门)→ 仅纵向。模板内容路径形如\n * ['layers', i, 'template', 'cells', c, 'content']——'template' 段在\n * len−4(len−1 content、len−2 格下标、len−3 'cells'),按尾段回数定位;\n * - 行模板替身(路径尾 'template'):声明体尺寸不经画布柄改(转换入口/面板\n * 语义面),双轴皆禁。\n */\nexport function resizableAxesAt(doc: Canvas, path: LayerPath): ResizeAxes {\n const layer = resolveLayer(doc, path)\n if (!layer) return NO_AXES\n if (path[path.length - 1] === 'content') {\n const templateContent = path.length > 4 && path[path.length - 4] === 'template'\n return templateContent ? { width: false, height: true } : NO_AXES\n }\n if (path[path.length - 1] === 'template') return NO_AXES\n const containerKey = path[path.length - 2]\n if (containerKey === 'rows') return { width: false, height: true }\n if (containerKey === 'cells') return { width: true, height: true }\n return { width: true, height: true }\n}\n\n/**\n * 路径处图层的可用柄集合:柄的每一条作用轴都在可缩放面内才可用——角柄要求\n * 双轴皆可(行上角柄退化成边柄会造成「拖角只动一边」的歧义,不出),gizmo\n * 渲染与手势起点共用(beginResize 同缝拒绝不可用柄)。\n */\nexport function resizeHandlesAt(doc: Canvas, path: LayerPath): readonly ResizeHandle[] {\n const axes = resizableAxesAt(doc, path)\n return RESIZE_HANDLES.filter((handle) => {\n const needsWidth = handleResizesWidth(handle)\n const needsHeight = handleResizesHeight(handle)\n return (!needsWidth || axes.width) && (!needsHeight || axes.height)\n })\n}\n\n/** 手柄命中半径(css 像素,缩放无关,场景半径 = 半径 / zoom;视觉柄 8px 见 gizmo) */\nexport const RESIZE_HANDLE_HIT_PX = 6\n\n/** 柄在盒上的中心点(场景坐标:四角 + 四边中点;gizmo 柄绘制同源消费) */\nexport function resizeHandlePoint(\n box: ResizeBoxGeometry,\n handle: ResizeHandle,\n): { x: number; y: number } {\n const right = box.x + box.width\n const bottom = box.y + box.height\n switch (handle) {\n case 'nw':\n return { x: box.x, y: box.y }\n case 'n':\n return { x: box.x + box.width / 2, y: box.y }\n case 'ne':\n return { x: right, y: box.y }\n case 'e':\n return { x: right, y: box.y + box.height / 2 }\n case 'se':\n return { x: right, y: bottom }\n case 's':\n return { x: box.x + box.width / 2, y: bottom }\n case 'sw':\n return { x: box.x, y: bottom }\n case 'w':\n return { x: box.x, y: box.y + box.height / 2 }\n }\n}\n\n/**\n * 场景点命中可用柄:距柄中心 ≤ 屏幕半径折算的场景半径即命中;多柄同时命中\n * (极小盒边中点与角点重合)取 RESIZE_HANDLES 次序的先者。只测传入的可用柄\n * 集合(内核 resizeHandlesAt 的投影),不在集合内的柄不命中。\n */\nexport function resizeHandleAt(\n handles: readonly ResizeHandle[],\n box: ResizeBoxGeometry,\n sceneX: number,\n sceneY: number,\n zoom: number,\n): ResizeHandle | null {\n const tolerance = RESIZE_HANDLE_HIT_PX / zoom\n for (const handle of handles) {\n const point = resizeHandlePoint(box, handle)\n if (Math.hypot(sceneX - point.x, sceneY - point.y) <= tolerance) return handle\n }\n return null\n}\n","/**\n * 数据源 schema 方言编译器(content-completion 工单 02/08,spec §2 + §6 D6–D8)。\n *\n * D6 钉定:注入边界一次性转译为内部形状树(不在查询期惰性解析原始 schema,\n * 行业惯例见 schema-completion-research.md)。方言子集 = JSON Schema draft-07 的\n * 结构关键词(`properties` / `items` / `type`)+ 注解关键词(`description` /\n * `title` / `additionalProperties: true` / 白名单 `format`,见下)+ 引用关键词\n * (`$ref` 内部 JSON Pointer、`definitions`/`$defs` 命名池);其余关键词\n * (`required`/`enum`/`const`/`allOf` 族/`patternProperties`/`$schema`/`$id` 及\n * 一切未识别键)宽松忽略,节点按已识别字段归一(可能变叶子)。\n *\n * 声明契约(D1 钉定):schema 声明即 `compile(canvas, dataset)` 收到的 data\n * 载荷形状——根上下文候选 = 载荷顶层键;根必须是带 properties 的对象声明。\n *\n * format 注解(expression-type-methods 工单 11/12,content-completion spec D13):\n * draft-07 `format` 从宽松忽略清单开口——仅精确匹配小写 `date` / `date-time`\n * (draft-07 格式词表子集)且节点 type 归一后为 string 才挂注解;其余值(大小写\n * 变体/其他词表值/自定义值)/非 string 节点/形态不符维持宽松忽略,**无诊断**。\n * 注解只影响建议面(date() 建议分桶),求值器/graph/wire 三端零感知。\n *\n * 降级分级(D7):\n * - **整份拒绝**仅保留根级结构性问题(在原始文档上判定,拒绝面与 walker 时代\n * 全等):根非对象 / 根缺 properties 层级(含根级 `$ref`——根不解引用)/\n * `reserved_root_key`(根级键 `row` 或 `$` 前缀,前移填充期硬错误,对应\n * `CanvasHydrator::assertReservedRootKeys`;仅根级保留,嵌套层同名键合法)。\n * - **局部故障**(`$ref` 断链 / 外部指针 / 环引用(活跃路径 seen-set 判定)/\n * 目标形态不符)→ 该节点降级为叶子(保留本地 type/description/title 注解)+\n * diagnostics 逐条记录,树其余部分照常服务;注入期由\n * normalizeExpressionSchemaSource 汇总 console.warn 一次(D12 单点收口,\n * 诊断明细可经 parseExpressionSchema 直取,工单 11 面板展示)。\n * - 深层识别关键词**形态不符**(如 type: 3 / property 子声明非对象)视同未识别\n * 键宽松忽略:不诊断不降级,节点按剩余字段归一(可能变叶子)。\n *\n * $ref 语义(D7):任意内部 `#/` JSON Pointer(先整段百分号解码——`%25`→`%` 等,\n * 解码失败记 broken_ref——再按 `/` 切段、逐段 `~1`→`/`、`~0`→`~` 反转义),\n * definitions/$defs 命名池只作指针目标不进候选树;外部/远程指针不支持,归宿主\n * 预内联。目标为基底,本地 `description` 覆盖目标同名注解(对 draft-07 兄弟语义\n * 的唯一偏离——兄弟 description 是现成候选说明,严格性无收益;其余兄弟关键词按\n * draft-07 忽略)。环引用防环用活跃路径 seen-set(对齐 VS Code\n * resolveSchemaContent 范式):seen 只含当前 DFS 祖先链,菱形引用不误判;\n * 该守卫兼防宿主程序化构造的对象别名自嵌套(JSON 文档天然无环)。\n *\n * D3 钉定(2026-09-30 由 D9 修订):数组类型节点 items 键树下钻保留在**下钻层**\n * (schemaNodeAtPath / resolveRowSchema,rowsPath 行 schema 解析与 `row.*`/`$index`\n * 机制零影响);**枚举层**(schemaChildEntries 候选视图)已分叉——数组节点\n * (type 声明为 array 或声明了 items)收敛为叶子、不沿 items 链穿透:具名段下钻\n * 数组在求值器(InterpolationEvaluator::navigateValue)是静默空串(缺字段信号),\n * 补全不得怂恿写渲染为空白的表达式;类型徽标仍显示 array 本身,不特供「供表格\n * rowsPath 使用」文案(为表达式内置函数留前瞻口,spec D9)。open 节点\n * (additionalProperties: true)即随枚举结果携带 open 信号(D10,供工单 10 浮层\n * 占位提示;信号面与候选空否无关)。\n *\n * 全模块零 DOM、零内部依赖(editor-shared-isolation);除\n * normalizeExpressionSchemaSource 的 console 警告收口(注入期汇总一次)外均为纯函数。\n */\n\n/** schema 方言形状树节点(parse 归一化产物,properties 冻结为 Map 防宿主变异) */\nexport interface ExpressionSchemaNode {\n /** 主类型徽标数据:type 关键词归一(数组取首个非 null;未声明为 undefined) */\n type?: string\n /** 展示回落注解(D8):浮层取 description ?? title */\n title?: string\n description?: string\n /** 开放映射标记(D10):`additionalProperties: true`;动态字段实际键由宿主合入 properties */\n open?: boolean\n /**\n * 日期标记注解(工单 11,D13):`format: \"date\" | \"date-time\"` 白名单两值、\n * 仅 type 归一为 string 的节点挂;只影响 date() 建议分桶,永不触发诊断\n */\n format?: 'date' | 'date-time'\n properties?: ReadonlyMap<string, ExpressionSchemaNode>\n items?: ExpressionSchemaNode\n}\n\n/** schema 子集一条目(枚举子候选用:键名 + 节点元信息) */\nexport interface ExpressionSchemaChildEntry {\n key: string\n node: ExpressionSchemaNode\n}\n\n/** 声明期拒绝原因:reserved_root_key = 前移的填充期硬错误;invalid_schema = 形态非法 */\nexport type ExpressionSchemaRejectReason = 'reserved_root_key' | 'invalid_schema'\n\n/** 局部故障诊断码(D7 四类,全部 $ref 解析面;节点降叶子 + 树其余照常服务) */\nexport type ExpressionSchemaDiagnosticCode = 'broken_ref' | 'external_ref' | 'circular_ref' | 'target_shape_mismatch'\n\n/** 一条局部故障诊断:path = 降级节点在原始 schema 文档中的声明路径(JSON Pointer 风格) */\nexport interface ExpressionSchemaDiagnostic {\n path: string\n code: ExpressionSchemaDiagnosticCode\n detail: string\n}\n\nexport type ExpressionSchemaParse =\n | { ok: true; schema: ExpressionSchemaNode; diagnostics: readonly ExpressionSchemaDiagnostic[] }\n | { ok: false; reason: ExpressionSchemaRejectReason; detail: string }\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** type 关键词归一:string 直取;string[] 取首个非 null/空串(全 null/空为 undefined) */\nfunction normalizeSchemaType(value: string | readonly string[]): string | undefined {\n if (typeof value === 'string') return value === '' ? undefined : value\n const primary = value.find((entry) => entry !== 'null' && entry !== '')\n return primary\n}\n\n/** 节点注解子形状(降级叶子 = 仅注解) */\ntype SchemaAnnotations = Pick<ExpressionSchemaNode, 'type' | 'description' | 'title' | 'format'>\n\n/** format 注解白名单(工单 11:精确匹配小写两值,其余宽松忽略) */\nconst FORMAT_ANNOTATIONS: ReadonlySet<string> = new Set(['date', 'date-time'])\n\n/** 节点注解的宽松提取:形态不符的键视同未识别忽略(工单 08 宽松面) */\nfunction annotationsOf(raw: Record<string, unknown>): SchemaAnnotations {\n const node: SchemaAnnotations = {}\n const rawType = raw['type']\n if (typeof rawType === 'string') {\n const type = normalizeSchemaType(rawType)\n if (type !== undefined) node.type = type\n } else if (Array.isArray(rawType)) {\n // spec §2:string[] 取首个非 null;非 string 元素宽松剔除(形态不符不整体作废)\n const type = normalizeSchemaType(rawType.filter((entry): entry is string => typeof entry === 'string'))\n if (type !== undefined) node.type = type\n }\n const rawDescription = raw['description']\n if (typeof rawDescription === 'string') node.description = rawDescription\n const rawTitle = raw['title']\n if (typeof rawTitle === 'string') node.title = rawTitle\n // format 注解(D13):白名单两值 + 节点归一 type 为 string 才挂;其余值/形态\n // 不符宽松忽略无诊断(只影响建议面,求值三端零感知)\n const rawFormat = raw['format']\n if (node.type === 'string' && typeof rawFormat === 'string' && FORMAT_ANNOTATIONS.has(rawFormat)) {\n node.format = rawFormat as ExpressionSchemaNode['format']\n }\n return node\n}\n\n/** 声明路径段转义(JSON Pointer 键级 ~0/~1),仅供诊断 path 展示 */\nfunction pointerEscape(key: string): string {\n return key.replace(/~/g, '~0').replace(/\\//g, '~1')\n}\n\n/** JSON Pointer 键级反转义(RFC 6901):单趟左到右,~1→/、~0→~;未跟 0/1 的 ~ 按字面保留 */\nfunction unescapePointerToken(token: string): string {\n let out = ''\n for (let i = 0; i < token.length; i += 1) {\n const ch = token[i]\n if (ch === '~' && (token[i + 1] === '0' || token[i + 1] === '1')) {\n out += token[i + 1] === '0' ? '~' : '/'\n i += 1\n } else {\n out += ch\n }\n }\n return out\n}\n\ntype InternalRefResolution = { ok: true; target: unknown } | { ok: false; why: string }\n\n/**\n * 内部 `#/` 指针解析(仅纯对象键导航):先整段百分号解码(URI 片段层,`%25`→`%`;\n * 无效编码=作者错误,显式断链不静默),再切段、逐段反转义。空片段(`#`)= 根文档。\n */\nfunction resolveInternalRef(ref: string, root: Record<string, unknown>): InternalRefResolution {\n let fragment = ref.slice(1)\n try {\n fragment = decodeURIComponent(fragment)\n } catch {\n return { ok: false, why: '指针片段含无效百分号编码' }\n }\n // 剥掉前导 '/'(JSON Pointer 以 / 起段);空片段(#)= 根文档零段\n const tokens = fragment === '' ? [] : fragment.replace(/^\\//, '').split('/')\n let current: unknown = root\n for (const token of tokens) {\n if (!isPlainObject(current)) return { ok: false, why: `指针途经非对象(段 \"${token}\")` }\n const key = unescapePointerToken(token)\n if (!(key in current)) return { ok: false, why: `指针段未命中(\"${token}\")` }\n current = current[key]\n }\n return { ok: true, target: current }\n}\n\ninterface CompileContext {\n /** 原始 schema 文档($ref 指针导航根) */\n root: Record<string, unknown>\n diagnostics: ExpressionSchemaDiagnostic[]\n /** 活跃路径 seen-set:当前 DFS 祖先链上的原始对象(环引用判定,菱形不误判) */\n activePath: Set<object>\n}\n\n/**\n * 局部故障统一落点(D7):记一条诊断 + 该节点降级为叶子——结构丢弃($ref 目标\n * 本该提供),保留本地注解(type/description/title)供候选展示。\n */\nfunction degradeWithDiagnostic(\n ctx: CompileContext,\n raw: Record<string, unknown>,\n path: string,\n code: ExpressionSchemaDiagnosticCode,\n detail: string,\n): ExpressionSchemaNode {\n ctx.diagnostics.push({ path, code, detail })\n return annotationsOf(raw)\n}\n\nfunction compileSchemaNode(raw: Record<string, unknown>, declPath: string, ctx: CompileContext): ExpressionSchemaNode {\n if (ctx.activePath.has(raw)) {\n // $ref 环由引用点先行判定落诊断(path 指向引用者更可定位);此守卫兜住\n // 无 $ref 的对象别名自嵌套(程序化构造才可能,JSON 文档天然无环)\n return degradeWithDiagnostic(ctx, raw, declPath, 'circular_ref', '节点在活跃路径上重入(环引用),降级为叶子')\n }\n ctx.activePath.add(raw)\n try {\n const rawRef = raw['$ref']\n if (rawRef !== undefined) return compileRefNode(raw, rawRef, declPath, ctx)\n return compileInlineNode(raw, declPath, ctx)\n } finally {\n ctx.activePath.delete(raw)\n }\n}\n\n/** $ref 节点(D7):目标为基底,本地 description 覆盖;解析失败按故障分类降叶子 */\nfunction compileRefNode(raw: Record<string, unknown>, rawRef: unknown, declPath: string, ctx: CompileContext): ExpressionSchemaNode {\n if (typeof rawRef !== 'string') {\n return degradeWithDiagnostic(ctx, raw, declPath, 'broken_ref', '$ref 必须是 string,节点降级为叶子')\n }\n if (!rawRef.startsWith('#')) {\n return degradeWithDiagnostic(\n ctx,\n raw,\n declPath,\n 'external_ref',\n `$ref 指向外部/远程目标 \"${rawRef}\"(仅支持内部 #/ 指针,归宿主预内联),节点降级为叶子`,\n )\n }\n const resolved = resolveInternalRef(rawRef, ctx.root)\n if (!resolved.ok) {\n return degradeWithDiagnostic(ctx, raw, declPath, 'broken_ref', `$ref 内部指针解析失败(${resolved.why}),节点降级为叶子`)\n }\n if (!isPlainObject(resolved.target)) {\n return degradeWithDiagnostic(ctx, raw, declPath, 'target_shape_mismatch', `$ref 目标不是 schema 对象,节点降级为叶子`)\n }\n if (ctx.activePath.has(resolved.target)) {\n return degradeWithDiagnostic(ctx, raw, declPath, 'circular_ref', `$ref 目标在活跃路径上(环引用)\"${rawRef}\",节点降级为叶子`)\n }\n // 基底编译(诊断 path 沿引用指针走,指向定义本体);每次 deref 独立编译,无共享可变状态\n const base = compileSchemaNode(resolved.target, rawRef, ctx)\n const localDescription = raw['description']\n if (typeof localDescription === 'string') base.description = localDescription\n return base\n}\n\n/** 无 $ref 节点:注解 + open 标记 + 结构关键词(形态不符的键宽松忽略) */\nfunction compileInlineNode(raw: Record<string, unknown>, declPath: string, ctx: CompileContext): ExpressionSchemaNode {\n const node: ExpressionSchemaNode = annotationsOf(raw)\n\n if (raw['additionalProperties'] === true) node.open = true\n\n const rawProperties = raw['properties']\n if (isPlainObject(rawProperties)) {\n const properties = new Map<string, ExpressionSchemaNode>()\n for (const [key, child] of Object.entries(rawProperties)) {\n if (!isPlainObject(child)) continue // 子声明非对象宽松跳过,兄弟键照常\n properties.set(key, compileSchemaNode(child, `${declPath}/properties/${pointerEscape(key)}`, ctx))\n }\n node.properties = properties\n }\n\n const rawItems = raw['items']\n if (isPlainObject(rawItems)) node.items = compileSchemaNode(rawItems, `${declPath}/items`, ctx)\n\n return node\n}\n\n/**\n * 声明期编译(D6 注入边界一次性转译):合法 → 节点树 + diagnostics(局部故障\n * 逐条,可为空);根级保留键 → reserved_root_key;根级结构非法 → invalid_schema。\n * 根级判定在原始文档上进行(根级 `$ref` 不解引用,拒绝面与 walker 时代全等)。\n */\nexport function parseExpressionSchema(raw: unknown): ExpressionSchemaParse {\n if (!isPlainObject(raw)) {\n return { ok: false, reason: 'invalid_schema', detail: 'schema 根必须是对象声明' }\n }\n\n // 保留键检查先于形态细化:键名问题无需信任深层结构(对应填充期 reserved_root_key 硬错误)\n const rawProperties = raw['properties']\n if (isPlainObject(rawProperties)) {\n for (const key of Object.keys(rawProperties)) {\n if (key === 'row' || key.startsWith('$')) {\n return { ok: false, reason: 'reserved_root_key', detail: `根级键 \"${key}\" 保留(row. 前缀/$ 名与求值上下文冲突)` }\n }\n }\n }\n if (!isPlainObject(rawProperties)) {\n return { ok: false, reason: 'invalid_schema', detail: '根声明缺 properties 层级(schema 即载荷形状,必须声明顶层键)' }\n }\n\n const diagnostics: ExpressionSchemaDiagnostic[] = []\n const schema = compileSchemaNode(raw, '#', { root: raw, diagnostics, activePath: new Set() })\n return { ok: true, schema, diagnostics }\n}\n\n/**\n * 注入缝收口(工单 03 接线用):parse 失败降级为 null + console.warn(含原因,\n * 不抛错不弹错);编译成功而存在局部故障诊断 → 汇总 warn **恰一次**(D7/D12:\n * 内核单点收口,不逐条告警;诊断明细可经 parseExpressionSchema 直取供工单 11\n * 面板展示)。注入期调用一次,枚举期拿 null 直接无候选、不再逐键告警。\n */\nexport function normalizeExpressionSchemaSource(raw: unknown): ExpressionSchemaNode | null {\n const parsed = parseExpressionSchema(raw)\n if (!parsed.ok) {\n console.warn(\n `[canvas-editor] 数据源 schema 声明降级为无候选(${parsed.reason}: ${parsed.detail})——补全是辅助声明不作权威,不弹错`,\n )\n return null\n }\n if (parsed.diagnostics.length > 0) {\n const lines = parsed.diagnostics.map((diagnostic) => `- ${diagnostic.path}:${diagnostic.code}——${diagnostic.detail}`)\n console.warn(\n `[canvas-editor] 数据源 schema 编译诊断 ${parsed.diagnostics.length} 条(局部降级为叶子,树其余部分照常服务):\\n${lines.join('\\n')}`,\n )\n }\n return parsed.schema\n}\n\n/**\n * 节点的对象形态视图(下钻层专用机制,D3;枚举层候选视图已按 D9 分叉——见\n * schemaChildEntries):properties 声明优先(声明而段未命中 = 死分支,不回退\n * items);无 properties 时沿 items 链找首个带 properties 的节点(数组的数组\n * 穿透,rowsPath 行 schema 解析依赖此链);全链无 = null(叶子分支)。\n */\nfunction objectShapeOf(node: ExpressionSchemaNode): ReadonlyMap<string, ExpressionSchemaNode> | null {\n let cursor: ExpressionSchemaNode | undefined = node\n while (cursor !== undefined) {\n if (cursor.properties !== undefined) return cursor.properties\n cursor = cursor.items\n }\n return null\n}\n\n/**\n * 点路径下钻 walker:逐段经对象形态视图解析(见 objectShapeOf)。全程不抛错:\n * schema 与数据源可能漂移,walker 只管声明树。\n */\nexport function schemaNodeAtPath(node: ExpressionSchemaNode, segments: readonly string[]): ExpressionSchemaNode | null {\n let current = node\n for (const segment of segments) {\n const child = objectShapeOf(current)?.get(segment)\n if (child === undefined) return null\n current = child\n }\n return current\n}\n\n/**\n * 一层子候选枚举结果(D9 候选视图的返回面):候选条目 + open 信号。信号走结果\n * 面扩展,不污染候选条目契约(ExpressionSchemaChildEntry 不加字段)。\n */\nexport interface ExpressionSchemaChildView {\n /** 一层子候选(按声明序);数组节点收敛为叶子 = 空(D9) */\n entries: readonly ExpressionSchemaChildEntry[]\n /** 开放映射信号(D10):节点标 open(additionalProperties: true)时为 true;非 open 恒缺省 */\n open?: boolean\n}\n\n/**\n * 一层子候选枚举(枚举层候选视图,D9 分叉点):数组节点(type 声明为 array 或\n * 声明了 items)**一律**收敛为叶子——具名段下钻数组在求值器是静默空串\n * (navigateValue 缺字段信号),补全不得怂恿写渲染为空白的表达式(spec §2 D9,\n * 2026-09-30 修订 D3);声明自相矛盾(type array 而又带 properties/items)时从疑\n * 同样收敛,对齐 D7 疑点降叶哲学。其余节点取**自身** properties(按声明序),\n * 不沿 items 链穿透。open 信号(D10)随视图透出,供浮层占位提示(工单 10)。\n * 下钻层(schemaNodeAtPath/resolveRowSchema)机制不动,rowsPath/`row.*`/`$index`\n * 零影响(数组的数组穿透不回归)。\n */\nexport function schemaChildEntries(node: ExpressionSchemaNode): ExpressionSchemaChildView {\n if (node.type === 'array' || node.items !== undefined) return { entries: [] }\n const entries =\n node.properties !== undefined ? [...node.properties].map(([key, child]) => ({ key, node: child })) : []\n return node.open === true ? { entries, open: true } : { entries }\n}\n\n/**\n * 行上下文行键树解析(rowsPath 数组 items,CanvasHydrator 行注入 'row' 的形状\n * 本体):rowsPath 走点路径下钻(下钻层 items 链机制,D9 分叉不影响——数组的\n * 数组穿透不回归),命中数组节点取 items;节点无 items(schema 未声明行形状或\n * 漂移)→ null(行子树无候选,结构头 row 仍可用)。\n * 嵌套模板表的行相对 rowsPath 以当前行 schema 为基准解析,由调用方传入对应根。\n */\nexport function resolveRowSchema(root: ExpressionSchemaNode, rowsPath: string): ExpressionSchemaNode | null {\n const segments = rowsPath.split('.')\n if (rowsPath === '' || segments.includes('')) return null\n const node = schemaNodeAtPath(root, segments)\n return node?.items ?? null\n}\n","/**\n * 路径段解析纯函数(content-completion 工单 01,spec §4 纯函数 ②)。\n *\n * 语义权威 = php-canvas-next `InterpolationEvaluator::resolveFragment`(只读对齐,\n * 不改求值器):head 三分类——`row`(行命名空间)/ `$root`(显式取根,与裸名等价)/\n * 裸键(含 `$index` 等 `$` 保留名)——+ 余段原序下钻;空片段、路径含空段\n * (`row..x` / `.x` / `x.`)= `expression_syntax_error` 信号(与求值器同码)。\n *\n * 契约:入参是**已 trim** 的片段表达式(trim 是片段扫描的职责,对齐求值器\n * 先 trim 后 resolve 的次序;本函数不再 trim,空白字符属于键名)。\n * 补全消费侧(schema walker,工单 02)处理「片段内输入 `.` 刷新候选」时,\n * 先剥掉单个尾点(补全点标记)再走本函数——`row.` 按求值器语义是空段错误,\n * 剥点后的 `row` 才是可下钻前缀。\n *\n * 全模块纯函数、无 DOM、零内部依赖(editor-shared-isolation)。\n */\n\n/** 与 PHP 侧 HydrateException 稳定错误码对齐(spec §5.2,三端一致性抓手) */\nexport type ExpressionSyntaxError = 'expression_syntax_error'\n\n/** head 三分类(对齐 resolveFragment 的 match 分支;大小写敏感,Row/$ROOT 是裸键) */\nexport type ExpressionPathHead =\n | { kind: 'row' }\n | { kind: 'root' }\n | { kind: 'bare'; name: string }\n\nexport type ExpressionPathParse =\n | { ok: true; head: ExpressionPathHead; rest: readonly string[] }\n | { ok: false; error: ExpressionSyntaxError }\n\n/** 解析点路径:head 分类 + 余段;语法不合法(空片段/空段)返回错误信号 */\nexport function parseExpressionPath(expr: string): ExpressionPathParse {\n if (expr === '') {\n return { ok: false, error: 'expression_syntax_error' }\n }\n\n const segments = expr.split('.')\n if (segments.includes('')) {\n return { ok: false, error: 'expression_syntax_error' }\n }\n\n const head = segments[0] as string\n const rest = segments.slice(1)\n if (head === 'row') return { ok: true, head: { kind: 'row' }, rest }\n if (head === '$root') return { ok: true, head: { kind: 'root' }, rest }\n return { ok: true, head: { kind: 'bare', name: head }, rest }\n}\n","/**\n * 最小调用形解析纯函数(expression-type-methods 工单 12,spec §1.1/§1.2 ⑦⑨)。\n *\n * JS 预览端最小文法面 = **结构面识别、名单面不校验**(spec §1.2 ⑨):\n * - 分类(⑦):trim 后首个 token(ASCII 特界符/空白前)其后紧跟可选空白与 `(` 即\n * 调用,否则路径——`foo(bar)` / `日期(bar)` / `foo (bar)` 均按调用分类(白名单\n * 校验后置),`{{date}}` 无 `(` 恒为路径,数据集根键叫 date 合法。\n * - 结构错(括号不平衡、空参数位、尾逗号、字符串未闭合)照 `expression_syntax_error`\n * 同码标红;首参必须为路径(§1.2 ① 文法层钉死)同面标红。\n * - **未知函数名不在 JS 标红**(PHP 权威端先发新函数、编辑器后跟期间避免「求值\n * 正常但编辑器假红」的名单漂移);签名面(参数个数/第二参类型/n 范围)同不校验。\n * - 算法逐字节镜像 php-canvas-next `InterpolationEvaluator::resolveFragment /\n * parseCallArgs / parseCallArg`(权威端):路径实参吞至 `,`/`)` 为止(段内空白\n * 属于键名,缺逗号非结构错)、字符串仅单引号 `''` 翻倍转义、数字无符号无指数。\n *\n * 字符集对 PHP 逐字对齐:跳空白用 PHP skipWs 四字符(\" \\t\\n\\r\");分类与\n * parseCallArg 的 trim 用 PHP trim() 默认六字符(PHP_TRIM_CHARS,与 expressionScan\n * 同源语义)。JS `String.trim()`/正则 `\\s` 含全角空白等 Unicode 集,会造成漂移勿用。\n *\n * 全模块纯函数、零 DOM;层内依赖仅 expressionPath(实参路径空段校验同码)。\n */\nimport { parseExpressionPath, type ExpressionSyntaxError } from './expressionPath'\n\n/** PHP trim() 默认字符集(与 expressionScan 的 PHP_TRIM_CHARS 同一语义面) */\nconst TRIM_CHARS = ' \\t\\n\\r\\0\\x0B'\n\n/** PHP skipWs 字符集(比 trim 少 NUL/垂直制表——权威端两套字符集本就不同,逐字对齐) */\nconst WS_CHARS = ' \\t\\n\\r'\n\n/** ⑦ 分类首 token 的终止字符(PHP 正则 [^()',.\\s]+ 的 ASCII \\s = 六字符) */\nconst HEAD_STOP_CHARS = `()',.${TRIM_CHARS}`\n\nfunction phpTrim(value: string): string {\n let start = 0\n let end = value.length\n while (start < end && TRIM_CHARS.includes(value[start] as string)) start += 1\n while (end > start && TRIM_CHARS.includes(value[end - 1] as string)) end -= 1\n return value.slice(start, end)\n}\n\nfunction skipWs(expr: string, pos: number): number {\n while (pos < expr.length && WS_CHARS.includes(expr[pos] as string)) pos += 1\n return pos\n}\n\n/** 实参形态(spec §1.1 实参 := 路径 | 字符串 | 数字;value = 归一后文本) */\nexport interface ExpressionCallArg {\n type: 'path' | 'string' | 'number'\n value: string\n}\n\nexport type ExpressionCallParse =\n | { ok: true; name: string; args: readonly ExpressionCallArg[] }\n | { ok: false; error: ExpressionSyntaxError }\n\n/**\n * ⑦ 片段分类:首 token 后紧跟可选空白与 `(` 即调用。expr 须已 trim(片段扫描\n * 职责,与 parseExpressionPath 契约一致)。\n */\nexport function isCallFragment(expr: string): boolean {\n let head = 0\n while (head < expr.length && !HEAD_STOP_CHARS.includes(expr[head] as string)) head += 1\n if (head === 0) return false\n let pos = head\n while (pos < expr.length && TRIM_CHARS.includes(expr[pos] as string)) pos += 1\n return expr[pos] === '('\n}\n\n/**\n * 完整调用形结构解析(scan 错误信号与补全路由共用):只判结构面 + 首参路径\n * (文法层),名单/签名面不校验。expr 须已 trim;非调用形态由调用方分类后进入。\n */\nexport function parseExpressionCall(expr: string): ExpressionCallParse {\n let head = 0\n while (head < expr.length && !HEAD_STOP_CHARS.includes(expr[head] as string)) head += 1\n if (head === 0) return { ok: false, error: 'expression_syntax_error' }\n const name = expr.slice(0, head)\n\n // 实参表骨架(镜像 parseCallArgs):skipWs 四字符后必须 (,尾随内容即结构错\n let pos = skipWs(expr, head)\n if (pos >= expr.length || expr[pos] !== '(') return { ok: false, error: 'expression_syntax_error' }\n pos = skipWs(expr, pos + 1)\n\n const args: ExpressionCallArg[] = []\n if (pos < expr.length && expr[pos] === ')') {\n pos += 1\n } else {\n for (;;) {\n if (pos >= expr.length) return { ok: false, error: 'expression_syntax_error' }\n const arg = parseCallArg(expr, pos)\n if (!arg.ok) return arg\n args.push(arg.value)\n pos = skipWs(expr, arg.end)\n if (pos >= expr.length) return { ok: false, error: 'expression_syntax_error' }\n if (expr[pos] === ',') {\n pos = skipWs(expr, pos + 1)\n if (pos < expr.length && expr[pos] === ')') {\n return { ok: false, error: 'expression_syntax_error' } // 空参数位\n }\n continue\n }\n if (expr[pos] === ')') {\n pos += 1\n break\n }\n return { ok: false, error: 'expression_syntax_error' } // 调用实参语法错\n }\n }\n\n if (skipWs(expr, pos) !== expr.length) return { ok: false, error: 'expression_syntax_error' } // 调用后有多余内容\n\n // 首参文法层钉死为路径(§1.2 ①):'格式化常量' 是伪场景,误用更早一层被抓住\n if (args[0] !== undefined && args[0].type !== 'path') return { ok: false, error: 'expression_syntax_error' }\n // 路径实参空段与求值器同码(resolveCallArgValue 的 explode + 空段检查前移至此)\n for (const arg of args) {\n if (arg.type === 'path' && !parseExpressionPath(arg.value).ok) {\n return { ok: false, error: 'expression_syntax_error' }\n }\n }\n\n return { ok: true, name, args }\n}\n\ntype CallArgParse = { ok: true; value: ExpressionCallArg; end: number } | { ok: false; error: ExpressionSyntaxError }\n\n/** 单实参解析(镜像 parseCallArg 三分派):' 串 / 数字 / 路径吞至 ,/) 止后 trim */\nfunction parseCallArg(expr: string, pos: number): CallArgParse {\n const len = expr.length\n const ch = expr[pos] as string\n\n // 字符串字面量:仅单引号,'' 翻倍转义;未闭合 = 结构错\n if (ch === \"'\") {\n let cursor = pos + 1\n let buf = ''\n for (;;) {\n if (cursor >= len) return { ok: false, error: 'expression_syntax_error' }\n if (expr[cursor] === \"'\") {\n if (cursor + 1 < len && expr[cursor + 1] === \"'\") {\n buf += \"'\"\n cursor += 2\n continue\n }\n cursor += 1\n break\n }\n buf += expr[cursor]\n cursor += 1\n }\n return { ok: true, value: { type: 'string', value: buf }, end: cursor }\n }\n\n // 数字字面量:无符号十进制 数字+ ['.' 数字+],无指数(`.` 仅在后随数字时消费)\n if (ch >= '0' && ch <= '9') {\n const start = pos\n const isDigitAt = (index: number): boolean => {\n const c = expr[index]\n return c !== undefined && c >= '0' && c <= '9'\n }\n while (isDigitAt(pos)) pos += 1\n if (pos < len && expr[pos] === '.' && isDigitAt(pos + 1)) {\n pos += 1\n while (isDigitAt(pos)) pos += 1\n }\n return { ok: true, value: { type: 'number', value: expr.slice(start, pos) }, end: pos }\n }\n\n // 路径:至顶层 ',' 或 ')' 止后 trim(段内空白属于键名,缺逗号非结构错)\n const start = pos\n while (pos < len && expr[pos] !== ',' && expr[pos] !== ')') pos += 1\n const raw = phpTrim(expr.slice(start, pos))\n if (raw === '') return { ok: false, error: 'expression_syntax_error' } // 空参数位\n return { ok: true, value: { type: 'path', value: raw }, end: pos }\n}\n\n/** 光标槽位(补全路由消费):光标 = expr 末尾(调用方喂片段的光标前缀) */\nexport interface ExpressionCallSlotContext {\n /** 函数名(⑦ 首 token;名单面不校验) */\n name: string\n /** 实参槽位(0 起) */\n index: number\n /** 槽内光标前文本(字符串槽 = 引号内原文;其余 = trim 后) */\n text: string\n /** 字符串字面量槽(date 格式串建议面) */\n stringSlot: boolean\n}\n\n/**\n * 光标所在实参槽定位:容忍截断前缀(调用未写完/字符串未闭合都是常态输入),\n * 只定位不判错——结构红标归 parseExpressionCall/扫描面。返回 null = 无建议语义的\n * 落点(非调用形态 / 调用已闭合)。\n */\nexport function expressionCallSlotAtEnd(expr: string): ExpressionCallSlotContext | null {\n let head = 0\n while (head < expr.length && !HEAD_STOP_CHARS.includes(expr[head] as string)) head += 1\n if (head === 0) return null\n let pos = skipWs(expr, head)\n if (pos >= expr.length || expr[pos] !== '(') return null\n pos = skipWs(expr, pos + 1)\n\n let index = 0\n for (;;) {\n if (pos >= expr.length) return { name: expr.slice(0, head), index, text: '', stringSlot: false }\n const ch = expr[pos] as string\n\n // 字符串实参:闭合则完整消费;引号未闭合 = 光标在串内(格式串建议面)\n if (ch === \"'\") {\n let cursor = pos + 1\n for (;;) {\n if (cursor >= expr.length) {\n return { name: expr.slice(0, head), index, text: expr.slice(pos + 1), stringSlot: true }\n }\n if (expr[cursor] === \"'\") {\n if (cursor + 1 < expr.length && expr[cursor + 1] === \"'\") {\n cursor += 2\n continue\n }\n cursor += 1\n break\n }\n cursor += 1\n }\n pos = cursor\n } else if (ch !== ',' && ch !== ')') {\n // 路径/数字实参:吞至顶层 ,/) 止;END = 光标在该槽内\n const argStart = pos\n while (pos < expr.length && expr[pos] !== ',' && expr[pos] !== ')') pos += 1\n if (pos >= expr.length) {\n return { name: expr.slice(0, head), index, text: phpTrim(expr.slice(argStart)), stringSlot: false }\n }\n }\n\n if (pos >= expr.length) return { name: expr.slice(0, head), index: index + 1, text: '', stringSlot: false }\n if (expr[pos] === ')') return null // 调用闭合:无建议\n // 顶层 ',':进入下一参槽\n index += 1\n pos = skipWs(expr, pos + 1)\n }\n}\n","/**\n * 片段扫描纯函数(content-completion 工单 01,spec §4 纯函数 ①)。\n *\n * 语义逐条对齐 php-canvas-next `InterpolationEvaluator::evaluate` 扫描循环(只读\n * 对齐,不改求值器):首个未闭合 `{{` 起剩余全字面、片段内容 trim、`{{}}` 不嵌套\n * (首个 `}}` 收口,内容含 `{{`)、片段外字面直通;空片段/路径空段 =\n * `expression_syntax_error` 信号(与求值器同码)。切片语义对调用片段零改动\n * (spec §1.2 ⑤:首个 `}}` 收口、扫描器引号不感知——片段内禁 `}}` 字面);闭合\n * 片段的错误信号按 ⑦ 分类分流(路径/调用,expression-type-methods 工单 12)。\n *\n * 与求值器的一处表示分叉(编辑器侧补全所需):未闭合 `{{` 在求值期按字面直通,\n * 在扫描结果里另立 **open 片段**(kind: 'open')——这正是「输入 `{{` 自动弹出」的\n * 触发态(spec §3),其 expr 是待输入路径;closed 片段仍按求值语义带错误信号。\n *\n * 位置字段是 JS 码元索引(end 独占)——与 PHP 字节索引不同空间,跨端共享 fixture\n * (expression-scan v1)只钉切分内容不钉位置,位置由本端派生并由单测锁死。\n * trim 字符集对齐 PHP `trim()` 默认六字符(\" \\t\\n\\r\\0\\x0B\"),**不是** JS 的\n * `String.trim()`(后者含全角空格等 Unicode 空白,会造成双端漂移)。\n *\n * 全模块纯函数、无 DOM(editor-shared-isolation)。\n */\nimport { isCallFragment, parseExpressionCall } from './expressionCall'\nimport { parseExpressionPath, type ExpressionSyntaxError } from './expressionPath'\n\n/** 字面直通段(不含任何 `{{`) */\nexport interface ExpressionScanLiteral {\n kind: 'literal'\n start: number\n end: number\n text: string\n}\n\n/** 闭合片段(含 `{{ }}` 外壳区域;raw 为外壳内原文,expr 为 trim 后路径) */\nexport interface ExpressionScanFragment {\n kind: 'fragment'\n start: number\n end: number\n raw: string\n expr: string\n /** 空片段/路径空段信号(与求值器 expression_syntax_error 同码);合法为 null */\n error: ExpressionSyntaxError | null\n}\n\n/** 未闭合 `{{` 起的输入中片段(求值期按字面;编辑器侧补全触发态) */\nexport interface ExpressionScanOpen {\n kind: 'open'\n start: number\n end: number\n /** 含 `{{` 外壳的原文(到串尾) */\n text: string\n /** `{{` 之后内容的 trim 结果(待输入路径,可能为空串) */\n expr: string\n}\n\nexport type ExpressionScanPart = ExpressionScanLiteral | ExpressionScanFragment | ExpressionScanOpen\n\nexport interface ExpressionScan {\n template: string\n /** 有序切分;parts 拼回恒等于 template(空字面一律省略) */\n parts: readonly ExpressionScanPart[]\n}\n\n/** PHP trim() 默认字符集(JS String.trim 的 Unicode 空白集与之不同,勿用);\n * 编辑器侧补全的接受手术同用此集(导出防按值复刻漂移,工单 04) */\nexport const PHP_TRIM_CHARS = ' \\t\\n\\r\\0\\x0B'\n\n/**\n * PHP trim() 语义(六字符集):片段 trim 与 vue 侧补全的 expr 前缀截取同用此集\n * (光标定位参数位,expression-type-methods 工单 12——单一事实源,勿按值复刻;\n * JS `String.trim()` 含全角空格等 Unicode 空白,会造成与 PHP 的双端漂移)。\n */\nexport function phpTrim(value: string): string {\n let start = 0\n let end = value.length\n while (start < end && PHP_TRIM_CHARS.includes(value[start] as string)) start += 1\n while (end > start && PHP_TRIM_CHARS.includes(value[end - 1] as string)) end -= 1\n return value.slice(start, end)\n}\n\n/**\n * 片段错误信号(工单 12 适配调用片段):⑦ 分类分流——路径走既有\n * parseExpressionPath;调用走最小结构面解析(括号不平衡/空参数位/字符串未闭合/\n * 首参非路径 = expression_syntax_error 同码;未知函数名不标红,名单面不校验)。\n * 空片段按路径分支落错(空片段/空路径段同码,与求值器一致)。\n */\nfunction fragmentSyntaxError(expr: string): ExpressionSyntaxError | null {\n if (expr === '') return 'expression_syntax_error'\n if (isCallFragment(expr)) {\n return parseExpressionCall(expr).ok ? null : ('expression_syntax_error' as const)\n }\n return parseExpressionPath(expr).ok ? null : ('expression_syntax_error' as const)\n}\n\n/** 扫描模板串的片段切分:闭合/未闭合/多片段/错误信号,语义对齐求值器扫描循环 */\nexport function scanExpressionFragments(template: string): ExpressionScan {\n const parts: ExpressionScanPart[] = []\n let pos = 0\n\n let open = template.indexOf('{{')\n while (open !== -1) {\n const close = template.indexOf('}}', open + 2)\n if (close === -1) {\n // 未闭合:剩余全字面(求值语义);编辑器侧立 open 片段供补全触发\n if (open > pos) {\n parts.push({ kind: 'literal', start: pos, end: open, text: template.slice(pos, open) })\n }\n const text = template.slice(open)\n parts.push({ kind: 'open', start: open, end: template.length, text, expr: phpTrim(text.slice(2)) })\n return { template, parts }\n }\n\n if (open > pos) {\n parts.push({ kind: 'literal', start: pos, end: open, text: template.slice(pos, open) })\n }\n const raw = template.slice(open + 2, close)\n const expr = phpTrim(raw)\n const error = fragmentSyntaxError(expr)\n parts.push({ kind: 'fragment', start: open, end: close + 2, raw, expr, error })\n pos = close + 2\n open = template.indexOf('{{', pos)\n }\n\n if (pos < template.length) {\n parts.push({ kind: 'literal', start: pos, end: template.length, text: template.slice(pos) })\n }\n return { template, parts }\n}\n\n/**\n * 光标所在片段判定(编辑器侧语义,PHP 无光标概念故不入跨端 fixture):\n * closed 片段含光标 ⟺ start < cursor < end(`}}` 之后即离开——输入 `}}` 关闭浮层);\n * open 片段含光标 ⟺ start < cursor <= end(串尾输入位恒在内)。\n */\nexport function expressionFragmentAtCursor(\n scan: ExpressionScan,\n cursor: number,\n): ExpressionScanFragment | ExpressionScanOpen | null {\n for (const part of scan.parts) {\n if (part.kind === 'literal') continue\n const inside = part.kind === 'open' ? cursor > part.start && cursor <= part.end : cursor > part.start && cursor < part.end\n if (inside) return part\n }\n return null\n}\n","/**\n * 对齐画布 action 族的几何核(layer-align-snap 工单 01,ADR 0011):\n * 「对齐画布/贴边/贴角」是把选中图层盒整体对齐到画布几何的通用动作——与属性\n * 面板的盒内对齐(写图层 spec 的 align 字段,移动盒内内容)是两类动作,并存\n * 不回退;不绑定任何具体图层或文档数据(专属贴角预设不做,ADR 0011)。\n *\n * 几何语义:目标盒坐标按 mode 公式确定——贴边零边距、贴角统一边距(缺省\n * ALIGN_CORNER_MARGIN_PX)、居中取精确中点;盒大于画布不钳位(领域语义:\n * position 偏移可为负、溢出摆放),公式原样落位。调用方(alignToCanvas)以\n * layerBoxAt 解析的图层盒为唯一几何来源,把「目标盒坐标 − 当前盒坐标」的位移\n * 写进 position 偏移(anchor 不动——盒坐标对 position 线性,anchorOffset 不\n * 参与差值)。全模块纯函数、无 DOM。\n */\nimport type { LayerBox } from '@hankchen/canvas'\n\n/** 对齐画布 mode:居中×2 + 贴边×4 + 贴角×4 */\nexport type AlignToCanvasMode =\n | 'left'\n | 'right'\n | 'top'\n | 'bottom'\n | 'h-center'\n | 'v-center'\n | 'corner-tl'\n | 'corner-tr'\n | 'corner-bl'\n | 'corner-br'\n\n/** alignToCanvas 的可选参数:贴角系统一边距 */\nexport interface AlignToCanvasOptions {\n /** 贴角边距(四角同一取值);缺省 ALIGN_CORNER_MARGIN_PX */\n margin?: number\n}\n\n/** 贴角缺省边距(像素,ADR 0011 钉定的通用缺省,不做每图层可配置面) */\nexport const ALIGN_CORNER_MARGIN_PX = 40\n\n/** 目标盒坐标的几何入参(LayerBox 的位置尺寸切片) */\nexport type AlignBoxGeometry = Pick<LayerBox, 'x' | 'y' | 'width' | 'height'>\n\n/**\n * mode → 对齐后的目标盒左上坐标(mode 只动一个轴时另一轴保持原值):贴边零边距\n * (left x=0、right x=画布宽−盒宽……)、居中 = (画布尺寸−盒尺寸)/2(精确值不\n * 取整)、贴角 = 统一边距。盒大于画布不钳位,负值即向反侧溢出。未知 mode 返回\n * null(运行时垃圾输入的空转面;TS 层由联合类型收窄)。\n */\nexport function alignToCanvasTarget(\n mode: AlignToCanvasMode,\n box: AlignBoxGeometry,\n canvasWidth: number,\n canvasHeight: number,\n margin: number,\n): { x: number; y: number } | null {\n switch (mode) {\n case 'left':\n return { x: 0, y: box.y }\n case 'right':\n return { x: canvasWidth - box.width, y: box.y }\n case 'top':\n return { x: box.x, y: 0 }\n case 'bottom':\n return { x: box.x, y: canvasHeight - box.height }\n case 'h-center':\n return { x: (canvasWidth - box.width) / 2, y: box.y }\n case 'v-center':\n return { x: box.x, y: (canvasHeight - box.height) / 2 }\n case 'corner-tl':\n return { x: margin, y: margin }\n case 'corner-tr':\n return { x: canvasWidth - box.width - margin, y: margin }\n case 'corner-bl':\n return { x: margin, y: canvasHeight - box.height - margin }\n case 'corner-br':\n return { x: canvasWidth - box.width - margin, y: canvasHeight - box.height - margin }\n default:\n return null\n }\n}\n","/**\n * 剪贴板内核语义(工单 14):复制/粘贴/创建副本的纯函数面。\n *\n * - 剪贴板是**会话级**(不碰 OS 剪贴板、不跨会话):条目 = 选中层子树的深拷贝\n * 快照 + 复制时点的绝对盒(粘贴落位的基准)。graph 领域类型是纯 JSON 形态\n * (往返恒等契约),深拷贝用 JSON 往返——与源文档树彻底断开引用,复制后的\n * 原件编辑不影响粘贴产物。\n * - 可复制类型 = 可落根层的类型(文/图/码/表)。行/格是容器内结构(数组序语义、\n * priority 不参与根层排序),v1 粘贴统一落根层,故不可复制;格内容(文本/图片/\n * 码)可复制——粘贴出表成为根层。\n * - 粘贴落位(alt-drag-paste 工单 02 真原位):目标盒左上 = 复制时点绝对盒 +\n * 偏移,按**根层语义**反解 position(锚点补偿用画布尺寸,见 pastePosition)——\n * 格内容的 position 是格内相对值,直接平移会落到画布左上;根层源在此式下退化\n * 为 position + 偏移。⌘V 粘贴偏移入参恒为零(重复粘贴同位叠放,递增偏移链已\n * 删):dx=0 时根层源 position 恒等原位(offset 相消)、格内容源落其视觉位置;\n * PASTE_OFFSET_PX 保留给 ⌘D/副本。z 序紧邻源层由消费面(editor.pasteFromClipboard\n * 解析 sourcePath)选择 insertRootLayerAdjacentInDraft / 退化置顶。\n * - 创建副本 = 复制态 + 固定一格偏移,**不覆盖剪贴板**(excalidraw 同款语义);\n * 连续副本基于当前选中的副本链式偏移,天然不重叠。\n */\n\nimport { anchorOffset, type Anchor, type Canvas, type Layer, type LayerBox, type LayerType } from '@hankchen/canvas'\n\nimport { resolveLayer, type LayerPath } from '../shared/layerPath'\n\n/** 粘贴/副本的位置偏移基数(场景像素,整数) */\nexport const PASTE_OFFSET_PX = 20\n\n/** 可落根层的类型(= 可复制类型):容器内的行/格除外 */\nconst ROOT_PASTEABLE_TYPES: ReadonlySet<LayerType> = new Set([\n 'TextLayer',\n 'ImageLayer',\n 'QrCodeLayer',\n 'TableLayer',\n])\n\n/** 类型是否可粘贴为根层(= 是否可复制) */\nexport function isRootPasteableType(type: LayerType): boolean {\n return ROOT_PASTEABLE_TYPES.has(type)\n}\n\n/**\n * 会话剪贴板条目(工单 14;alt-drag-paste 工单 02 收窄):源子树深拷贝 + 复制\n * 时点绝对盒(粘贴落位基准,格内容的格内相对 position 据此换算画布绝对落位)+\n * 源路径(粘贴时紧邻插入的解析凭据;已删/悬空/属他文档失配退化置顶)。\n */\nexport interface ClipboardEntry {\n layer: Layer\n sourceBox: LayerBox\n sourcePath: LayerPath\n}\n\n/** 路径处图层是否可复制(可解析且类型可落根层);剪贴板入口与菜单可用态共用 */\nexport function canCopyLayerAt(doc: Canvas, path: LayerPath): boolean {\n const layer = resolveLayer(doc, path)\n return layer !== null && isRootPasteableType(layer.type)\n}\n\n/**\n * 子树深拷贝:JSON 往返(领域类型纯 JSON 形态)。输入可能是 immer draft(复制\n * 发生在文档树读取时),JSON 序列化天然产出普通对象,与 draft/源树断开引用。\n */\nexport function cloneLayerSubtree<T extends Layer>(layer: T): T {\n return JSON.parse(JSON.stringify(layer)) as T\n}\n\n/**\n * 粘贴落位的 position 反解:目标盒左上 = 源绝对盒左上 + (dx, dy),按根层语义\n * (父盒 = 画布)回推 position = 目标 − anchorOffset(锚点, 画布尺寸, 盒尺寸)。\n * 根层源退化为 position + 偏移(offset 相消);格内容源把格内相对 position 换算\n * 成画布绝对落位——副本出现在源内容视觉位置附近而非画布左上。\n */\nexport function pastePosition(\n sourceBox: LayerBox,\n anchor: Anchor,\n docWidth: number,\n docHeight: number,\n dx: number,\n dy: number,\n): { x: number; y: number } {\n const offset = anchorOffset(anchor, docWidth, docHeight, sourceBox.width, sourceBox.height)\n return { x: sourceBox.x + dx - offset.x, y: sourceBox.y + dy - offset.y }\n}\n\n/**\n * 粘贴产物准备:深拷贝 + 按源绝对盒反解根层 position(语义见 pastePosition)。\n * 锚点保持不变(副本忠实于源),盒尺寸用复制时点的布局结果(autoHeight 文本\n * 与副本的布局结果一致)。\n */\nexport function prepareRootPaste<T extends Layer>(\n layer: T,\n sourceBox: LayerBox,\n docWidth: number,\n docHeight: number,\n dx: number,\n dy: number,\n): T {\n const copy = cloneLayerSubtree(layer)\n const position = pastePosition(sourceBox, copy.position.anchor, docWidth, docHeight, dx, dy)\n return { ...copy, position: { ...copy.position, ...position } } as T\n}\n","/**\n * 查找替换的内核纯函数(canvas-web-find-replace 工单 01,CONTEXT「查找替换」词条)。\n *\n * 扫描(scanTextMatches):沿视觉序遍历全画布未标记(expression === null)的\n * TextLayer.text,大小写敏感子串匹配——数组尾→头(hitTest 同门:视觉最上先命中)、\n * 表格递归下钻(rows/cells 尾→头、content 收尾),两形态都参与(V1 rows 格内容 +\n * 模板态 template.cells 格内容,template ⊕ rows 恰存其一);隐藏层参与(隐藏是\n * 渲染排除语义非保护语义);标记字段整字段豁免(字面前缀也不替换——改走双形态\n * 编辑器既有通道:解标→改→重标),QrCode.value / ImageLayer.src / 层名不参与。\n *\n * 替换(replaceHitsInDraft):in-draft 助手按「原文快照命中」应用——命中(path +\n * 偏移对)在事务前一次算定,同字段多命中按偏移降序写入(前面的偏移不受后面长度\n * 变化影响)、替换产物不重扫(replacement 含查询串不死循环);写值 text 直写 +\n * expression 保持 null(与文本编辑提交 updateData 同门——参与者本就未标记,\n * expression=null 副作用为零)。\n *\n * 全模块纯函数、无 DOM。\n */\nimport type { Canvas, Layer, TextLayer } from '@hankchen/canvas'\nimport type { Draft } from 'immer'\n\nimport { resolveLayer, type LayerPath } from '../shared/layerPath'\n\n/** 一处命中:字段路径 + 文本偏移对 [start, end)(同字段多命中按序不重叠) */\nexport interface FindTextHit {\n readonly path: LayerPath\n readonly start: number\n readonly end: number\n}\n\n/** 单字段收集全部命中:非重叠推进(步过命中段,\"aaa\" 查 \"aa\" 得一处) */\nfunction collectFieldHits(layer: TextLayer, path: LayerPath, query: string, out: FindTextHit[]): void {\n let from = 0\n for (;;) {\n const index = layer.text.indexOf(query, from)\n if (index === -1) return\n out.push({ path, start: index, end: index + query.length })\n from = index + query.length\n }\n}\n\n/** 视觉序下钻(与 hitTest 的 hitWalk 同一门:子层后画在上,尾→头遍历) */\nfunction collectLayerHits(layer: Layer, path: LayerPath, query: string, out: FindTextHit[]): void {\n switch (layer.type) {\n case 'TableLayer':\n // 模板态格内容参与(V1 rows 格内容 + 模板格内容两形态;XOR 恰存其一)\n if (layer.template) {\n for (let i = layer.template.cells.length - 1; i >= 0; i -= 1) {\n collectLayerHits(layer.template.cells[i]!, [...path, 'template', 'cells', i], query, out)\n }\n }\n for (let i = layer.rows.length - 1; i >= 0; i -= 1) {\n collectLayerHits(layer.rows[i]!, [...path, 'rows', i], query, out)\n }\n break\n case 'TableRowLayer':\n for (let i = layer.cells.length - 1; i >= 0; i -= 1) {\n collectLayerHits(layer.cells[i]!, [...path, 'cells', i], query, out)\n }\n break\n case 'TableRowTemplate':\n // 独立出现的行模板属非法 wire(契约禁止);宽容扫过不崩溃\n for (let i = layer.cells.length - 1; i >= 0; i -= 1) {\n collectLayerHits(layer.cells[i]!, [...path, 'cells', i], query, out)\n }\n break\n case 'TableCellLayer':\n if (layer.content) collectLayerHits(layer.content, [...path, 'content'], query, out)\n break\n case 'TextLayer':\n // 仅未标记(纯静态)字段参与——豁免判定零成本:expression === null 即静态\n if (layer.expression === null) collectFieldHits(layer, path, query, out)\n break\n case 'ImageLayer':\n case 'QrCodeLayer':\n // src(资源引用语义)/ value(URL 语义)不参与\n break\n }\n}\n\n/**\n * 全画布静态文本扫描:query 为空串恒零命中(查找条初态空转)。返回视觉序命中\n * 列表(同字段多命中按偏移升序),每次从 doc 现算、不驻留(免失配维护)。\n */\nexport function scanTextMatches(doc: Canvas, query: string): FindTextHit[] {\n const hits: FindTextHit[] = []\n if (query === '') return hits\n for (let i = doc.layers.length - 1; i >= 0; i -= 1) {\n collectLayerHits(doc.layers[i]!, ['layers', i], query, hits)\n }\n return hits\n}\n\n/**\n * in-draft 替换:应用事务前算定的原文快照命中,返回实际应用处数。同字段命中\n * 聚组后按偏移降序写入;快照失效(路径不可解析/类型不符/偏移越界)的命中静默\n * 跳过(文档在快照后被结构编辑的防御分支)。零应用时不产生文档变更(immer 对\n * 同值写不记 patch,事务空转不进历史)。\n */\nexport function replaceHitsInDraft(\n draft: Draft<Canvas>,\n hits: readonly FindTextHit[],\n replacement: string,\n): number {\n const byPath = new Map<string, FindTextHit[]>()\n for (const hit of hits) {\n const key = hit.path.map(String).join('\\u0000')\n const group = byPath.get(key)\n if (group) group.push(hit)\n else byPath.set(key, [hit])\n }\n let applied = 0\n for (const group of byPath.values()) {\n const layer = resolveLayer(draft, group[0]!.path) as Draft<TextLayer> | null\n if (!layer || layer.type !== 'TextLayer') continue\n let text = layer.text\n for (const { start, end } of [...group].sort((a, b) => b.start - a.start)) {\n if (start < 0 || end > text.length || start > end) continue\n text = text.slice(0, start) + replacement + text.slice(end)\n applied += 1\n }\n layer.text = text\n layer.expression = null\n }\n return applied\n}\n","/**\n * 图层面板的内核语义(工单 10)。\n *\n * 顺序映射(spec「Vue 绑定与面板」拍板,防反直觉):\n * - 渲染顺序 = graph.layers 数组序 = priority 降序(数组头先画垫底,越大越垫底);\n * - 面板从上到下 = 数组逆序:面板顶 = 视觉最上层 = 数组尾 = 最小 priority。\n *\n * 两套重排语义分立:\n * - 根层重排走 priority 中点插值(保持「数组按 priority 降序」的契约语义,\n * 保存再打开顺序不变);插到上下邻之间 → priority = ⌊(上邻 + 下邻) / 2⌋,\n * 置顶 = min − 1、置底 = max + 1;priority 恒为整数(wire 解码 intval 同门,\n * 小数会在保存→打开时漂移),无整数间隙/同值冲突时对全表做一次按视觉序的\n * 带间隙阶梯归一化重赋(patch 大,仅兜底)。\n * - 表格行重排直接改 rows 数组序(行/格/内容是嵌套数组序语义,绘制按序遍历,\n * priority 字段在容器内不参与排序)。\n *\n * 全模块纯函数、无 DOM;draft 侧变换经 immer 事务落库,查询侧只读。\n */\nimport type { Canvas, Layer, LayerType, TableLayer, TableCellLayer, TextLayer, TableRowLayer, TableRowTemplateLayer } from '@hankchen/canvas'\nimport type { Draft } from 'immer'\n\nimport { isLayerPath, isLockedPath, resolveLayer, type LayerPath } from '../shared/layerPath'\nimport { scanExpressionFragments } from '../shared/expressionScan'\n\n/** 图层在容器树里的角色(决定面板缩进与可拖动性);templateRow = 行模板替身节点 */\nexport type LayerOutlineRole = 'root' | 'row' | 'cell' | 'content' | 'templateRow'\n\n/** 面板大纲节点:领域子树的纯投影(无文档写语义) */\nexport interface LayerOutlineNode {\n readonly type: LayerType\n readonly path: LayerPath\n readonly role: LayerOutlineRole\n /**\n * 用户命名(layer-panel-ux 工单 09):原样携带文档 name(根层重命名后由此\n * 上屏),空串 = 未命名,显示层回落派生标签。行/格/内容同字段照带(不消费)。\n */\n readonly name: string\n /**\n * 可见性(layer-panel-ux 工单 10):原样携带文档 visible,根层面板行的眼睛\n * 钮状态与隐藏行降不透明度据此投影。行/格/内容同字段照带(不消费——显隐\n * 语义只在 LayerBase 面)。\n */\n readonly visible: boolean\n /**\n * 锁定投影(canvas-web-layer-lock 工单 02):isLockedPath 前缀判定——锁定的\n * 根层及其整棵子树(行/格/内容)都携 true。锁定态住 ui 分支(会话级、零\n * wire 键),经 buildLayerOutline 的 ui 分支入参喂入;面板锁定钮状态、锁定\n * 行样式与删除钮/菜单删除项置灰据此投影(内核空转为权威,UI 呈现一致)。\n */\n readonly locked: boolean\n /**\n * 表达式前置(layer-panel-expression-prefix 工单 01):带标记内容层(三内容层\n * 且 expression !== null)的全部闭合片段按 expr 重构 `{{…}}` 外壳、单空格连接,\n * 面板零计算直读;空串 = 无前置(未标记 / 非内容层),对齐 name 字段先例;\n * 全部片段无效得常量 `{{…}}`。全角色节点照带。\n */\n readonly expressionPrefix: string\n /** TableLayer 专属:是否模板态(+行按钮置灰与 title 的判定面,spec §2.5) */\n readonly templated?: boolean\n readonly children: readonly LayerOutlineNode[]\n}\n\n/** 容器子层清单:type → (key, 孩子取法);content 单叶;template 段无下标 */\nfunction childLists(layer: Layer): { key: 'rows' | 'cells' | 'content' | 'template'; layers: readonly Layer[] }[] {\n switch (layer.type) {\n case 'TableLayer':\n // 模板态表格:大纲进模板子树(spec §2.2,推翻决策 2026-09)——行模板\n // 节点走无下标 template 段;V1 表照旧 rows\n if (layer.template !== null) return [{ key: 'template', layers: [layer.template] }]\n return [{ key: 'rows', layers: layer.rows }]\n case 'TableRowLayer':\n return [{ key: 'cells', layers: layer.cells }]\n case 'TableCellLayer':\n return layer.content ? [{ key: 'content', layers: [layer.content] }] : []\n case 'ImageLayer':\n case 'TextLayer':\n case 'QrCodeLayer':\n return []\n case 'TableRowTemplate':\n // 行模板的格进大纲(spec §2.2):模板行是链上正式一级,格操作与 V1 同心智\n return [{ key: 'cells', layers: layer.cells }]\n }\n}\n\n/** 三内容层的数据表达式标记读取面:非内容层无 data 字段,恒 null */\nfunction markedExpressionOf(layer: Layer): string | null {\n switch (layer.type) {\n case 'TextLayer':\n case 'ImageLayer':\n case 'QrCodeLayer':\n return layer.expression\n default:\n return null\n }\n}\n\n/** 全部闭合片段无效时的前置占位(spec 决策 2:无闭合片段 / expr 全空) */\nconst EXPRESSION_PREFIX_PLACEHOLDER = '{{…}}'\n\n/**\n * 表达式前置计算(layer-panel-expression-prefix 工单 01,spec 决策 2/3):判据看\n * 层型 + 标记、不看 role——实例行格内容与行模板子树格内容同享。取全部闭合片段,\n * 按 expr(phpTrim 后路径,`row.` 前缀原样)重构外壳、单空格连接;expr 空的片段\n * 无效,全部无效得常量占位。纯函数、无 I/O、不回写文档。\n */\nfunction expressionPrefixFor(layer: Layer): string {\n const expression = markedExpressionOf(layer)\n if (expression === null) return ''\n const exprs = scanExpressionFragments(expression).parts.flatMap((part) =>\n part.kind === 'fragment' && part.expr !== '' ? [part.expr] : [],\n )\n if (exprs.length === 0) return EXPRESSION_PREFIX_PLACEHOLDER\n return exprs.map((expr) => `{{${expr}}}`).join(' ')\n}\n\nfunction outlineWalk(\n layer: Layer,\n path: LayerPath,\n role: LayerOutlineRole,\n lockedPaths: readonly LayerPath[],\n): LayerOutlineNode {\n const children: LayerOutlineNode[] = []\n for (const { key, layers } of childLists(layer)) {\n for (let i = 0; i < layers.length; i += 1) {\n const roleOfChild: LayerOutlineRole =\n key === 'rows' ? 'row' : key === 'cells' ? 'cell' : key === 'template' ? 'templateRow' : 'content'\n // content 与 template 段无下标(路径语法 [..., 'template'] / [..., 'cells', k, 'content'])\n const childPath: LayerPath =\n key === 'content'\n ? [...path, 'content']\n : key === 'template'\n ? [...path, 'template']\n : [...path, key, i]\n children.push(outlineWalk(layers[i]!, childPath, roleOfChild, lockedPaths))\n }\n }\n return {\n type: layer.type,\n path,\n role,\n name: layer.name,\n visible: layer.visible,\n locked: isLockedPath(path, lockedPaths),\n expressionPrefix: expressionPrefixFor(layer),\n ...(layer.type === 'TableLayer' ? { templated: layer.template !== null } : null),\n children,\n }\n}\n\n/**\n * 面板大纲:根层按视觉层级排列(数组逆序——面板顶部 = 视觉最上层 = 数组尾),\n * 表格内行/格/内容保持数组序(行 0 在视觉顶部、格 0 在左)。\n * lockedPaths 为 ui 分支入参(canvas-web-layer-lock 工单 02):锁定态不写 graph,\n * 投影按 isLockedPath 前缀判定携带到锁定子树全树;缺省 [](无锁投影)。\n */\nexport function buildLayerOutline(\n doc: Canvas,\n lockedPaths: readonly LayerPath[] = [],\n): readonly LayerOutlineNode[] {\n const roots: LayerOutlineNode[] = []\n for (let i = doc.layers.length - 1; i >= 0; i -= 1) {\n roots.push(outlineWalk(doc.layers[i]!, ['layers', i], 'root', lockedPaths))\n }\n return roots\n}\n\n// ---- 新增图层:编辑器缺省形态 ----\n\n/**\n * 新层样式(text-defaults 工单 01,CONTEXT「新层样式」词条):记忆「下一个新建\n * 文本层用什么文本样式族」的偏好槽形态——样式粘贴文本族同门的五字段(不含\n * angle,旋转角不是新层偏好)。字段与 TextLayer 顶层字段/shape.lineHeight 对齐。\n */\nexport interface NextTextStyle {\n font: string\n fontSize: number\n fontColor: string\n autowrap: boolean\n lineHeight: number\n}\n\n/** 文本预设档位(CONTEXT「文本预设」词条):heading 40 / subheading 28 / body 16 */\nexport type TextPresetId = 'heading' | 'subheading' | 'body'\n\n/** 文本预设常量(spec 决策 4):v1 只定字号——字体缺口由新层样式槽补 */\nexport const TEXT_PRESETS: readonly { id: TextPresetId; fontSize: number }[] = [\n { id: 'heading', fontSize: 40 },\n { id: 'subheading', fontSize: 28 },\n { id: 'body', fontSize: 16 },\n]\n\n/** 新层样式槽初值:自工厂 TextLayer 形态派生(工厂缺省是单一事实源,槽跟着走) */\nexport function defaultNextTextStyle(): NextTextStyle {\n const base = createDefaultLayer('TextLayer') as TextLayer\n return {\n font: base.font,\n fontSize: base.fontSize,\n fontColor: base.fontColor,\n autowrap: base.autowrap,\n lineHeight: base.shape.lineHeight,\n }\n}\n\n/**\n * 新层样式字段的宽容读逐字段收口(text-defaults 工单 01;评审合流——捕获与\n * prefs 恢复共用同一谓词,不再双写):合法字段进合并集,缺字/类型不符忽略\n * (调用方保留现值),未知键忽略。autowrap 严格布尔(票 01 口径「不符跳过」,\n * 不做真值强转);lineHeight 须为正有限数。载荷来自属性写入与宿主 JSON 快照,\n * 类型层信任不了,运行时验。\n */\nexport function pickNextTextStylePatch(raw: unknown): Partial<NextTextStyle> {\n const out: Partial<NextTextStyle> = {}\n if (raw === null || typeof raw !== 'object') return out\n const source = raw as Record<string, unknown>\n if (typeof source.font === 'string') out.font = source.font\n if (typeof source.fontSize === 'number' && Number.isFinite(source.fontSize)) out.fontSize = source.fontSize\n if (typeof source.fontColor === 'string') out.fontColor = source.fontColor\n if (typeof source.autowrap === 'boolean') out.autowrap = source.autowrap\n if (typeof source.lineHeight === 'number' && Number.isFinite(source.lineHeight) && source.lineHeight > 0) {\n out.lineHeight = source.lineHeight\n }\n return out\n}\n\n/**\n * 编辑器新建图层的缺省形态:可见尺寸(0×0 无法命中/抓取)、top-left 摆位、\n * 中性配色。与 wire 解码缺省是两回事(那是「手写 JSON 宽进」语义),这里是\n * 编辑器 UX 语义;priority 统一由 addRootLayer 按「置顶 min−1」赋值。\n *\n * textDefaults(text-defaults 工单 01):新层样式槽覆盖参,仅 TextLayer 臂逐\n * 字段生效(其余层型忽略)——只有画拉 endCreate 与面板新增 addRootLayer 两个\n * 入口传槽;模板表/表格结构的工厂调用不传,保持确定性缺省(spec 决策 3)。\n */\nexport function createDefaultLayer(type: LayerType, textDefaults?: Partial<NextTextStyle>): Layer {\n const base = {\n // name/visible 缺省态(layer-panel-ux 工单 01):未命名 + 可见,wire 键省略\n name: '',\n visible: true,\n priority: 0,\n shape: {\n width: 0,\n height: 0,\n autoWidth: false,\n autoHeight: false,\n lineHeight: 1.2,\n padding: { top: 0, bottom: 0, left: 0, right: 0 },\n border: { top: null, bottom: null, left: null, right: null },\n backgroundColor: null,\n },\n align: { horizontal: 'left', vertical: 'top' } as const,\n position: { anchor: 'top-left', x: 0, y: 0 } as const,\n }\n switch (type) {\n case 'TextLayer': {\n const layer: TextLayer = {\n ...base,\n type,\n shape: {\n ...base.shape,\n width: 200,\n height: 60,\n lineHeight: textDefaults?.lineHeight ?? base.shape.lineHeight,\n },\n text: '文本',\n expression: null,\n font: textDefaults?.font ?? '',\n fontSize: textDefaults?.fontSize ?? 24,\n fontColor: textDefaults?.fontColor ?? '#111827',\n angle: 0,\n autowrap: textDefaults?.autowrap ?? false,\n }\n return layer\n }\n case 'ImageLayer':\n return { ...base, type, shape: { ...base.shape, width: 200, height: 150 }, src: null, expression: null }\n case 'QrCodeLayer':\n return { ...base, type, shape: { ...base.shape, width: 120, height: 120 }, value: 'canvas-web', expression: null }\n case 'TableLayer':\n return { ...base, type, shape: { ...base.shape, width: 400, height: 120 }, template: null, rowsPath: '', rows: [] }\n case 'TableRowLayer':\n return { ...base, type, shape: { ...base.shape, width: 400, height: 60 }, cells: [] }\n case 'TableRowTemplate':\n // 行模板不单卖(不进新建菜单)——只随 createTemplateTable 装配;工厂臂\n // 为穷举完整性兜底\n return { ...base, type, shape: { ...base.shape, width: 400, height: 60 }, cells: [] }\n case 'TableCellLayer':\n return { ...base, type, shape: { ...base.shape, width: 200, height: 60 }, content: null }\n }\n}\n\n/**\n * 模板表工厂(spec §2.1):V1 表缺省形态(400×120)+ 行模板装配——auto 行(声明\n * 高按解码归一为 0、宽 := 表宽关 autoWidth)+ 满宽 60 高缺省格带缺省文本内容\n * (内容宽度同步、零高度耦合,ADR 0006)。rowsPath 由调用方必填(解码\n * rows_path_missing 硬约束,创建表单校验前置);缺省格 60 高保证空态预览行可见\n * 可点(合成高 = 最高格,03 票 E1 机制)。\n */\nexport function createTemplateTable(options: { rowsPath: string }): TableLayer {\n const table = createDefaultLayer('TableLayer') as TableLayer\n const templateBase = createDefaultLayer('TableRowTemplate') as TableRowTemplateLayer\n const cellBase = createDefaultLayer('TableCellLayer') as TableCellLayer\n const contentBase = createDefaultLayer('TextLayer') as TextLayer\n\n const cell: TableCellLayer = {\n ...cellBase,\n shape: { ...cellBase.shape, width: table.shape.width },\n content: { ...contentBase, shape: { ...contentBase.shape, width: table.shape.width, autoWidth: false } },\n }\n const template: TableRowTemplateLayer = {\n ...templateBase,\n shape: { ...templateBase.shape, width: table.shape.width, autoWidth: false, autoHeight: true, height: 0 },\n cells: [cell],\n }\n return { ...table, template, rows: [], rowsPath: options.rowsPath }\n}\n\n// ---- draft 侧结构变换(EditorSession 的 action 在 transact 内调用) ----\n\n/**\n * 「insert-before 原始序号 to」移动的公共守卫与落点折算:from/to 须为安全整数、\n * from ∈ [0, n)、to ∈ [0, n];to ∈ {from, from+1}(原位/相邻落点)为无操作。\n * 返回摘除自身后的插入下标;null = 无操作(n < 2 时任何入参都落入无操作集)。\n * 工单 12 的同行格重排复用同一守卫(与行重排同款语义)。\n */\nexport function moveGuard(n: number, from: number, to: number): number | null {\n if (!Number.isSafeInteger(from) || !Number.isSafeInteger(to)) return null\n if (from < 0 || from >= n || to < 0 || to > n) return null\n if (to === from || to === from + 1) return null\n return to <= from ? to : to - 1\n}\n\n/**\n * 根层重排(draft 原位变换):面板坐标(0 = 视觉最上)折算数组坐标做 splice,\n * 再对移动层做 priority 中点插值。from 为被拖面板序号,to 为「insert-before\n * 原始面板序号」落点(∈ [0, N];to = N 即面板底);to ∈ {from, from+1} 或越界\n * 为无操作。数组序全程保持 priority 降序(渲染/解码同源的不变量)。\n * 返回移动的 (from, to) 数组下标(供选择路径重映射);null = 无变化。\n */\nexport function moveRootLayerInDraft(\n draft: Draft<Canvas>,\n fromPanel: number,\n toPanel: number,\n): { from: number; to: number } | null {\n const layers = draft.layers\n const n = layers.length\n // 面板坐标 0 = 视觉最上 = 数组尾:数组下标 = n − 1 − 面板序号(含摘除后的落点折算)\n const reduced = moveGuard(n, fromPanel, toPanel)\n if (reduced === null) return null\n const arrFrom = n - 1 - fromPanel\n const arrTo = n - 1 - reduced\n\n const [movedRaw] = layers.splice(arrFrom, 1)\n const moved = movedRaw as Draft<Layer>\n layers.splice(arrTo, 0, moved)\n\n // 插值:落点的下邻(数组左,视觉垫底侧,priority 更大)/上邻(数组右,视觉最上\n // 侧,更小)——中点/置顶/置底与「无间隙归一化兜底」的门规见 midpointPriority。\n const below = arrTo > 0 ? layers[arrTo - 1]!.priority : null\n const above = arrTo < n - 1 ? layers[arrTo + 1]!.priority : null\n const priority = midpointPriority(below, above)\n\n if (priority !== null) {\n moved.priority = priority\n } else {\n normalizeRootPriorities(layers)\n }\n return { from: arrFrom, to: arrTo }\n}\n\n/**\n * 根层落点的 priority 折算(中点插值门规,moveRootLayerInDraft 与\n * insertRootLayerAdjacentInDraft 共用):below/above 为落点两侧邻层的 priority\n * (数组左 = 视觉垫底侧、值更大;数组右 = 视觉最上侧、值更小)。有整数间隙\n * (below − above ≥ 2)取整数中点;无间隙返回 null(调用方全表归一化兜底);\n * 缺一侧退化:无 above(落点视觉最顶)取 below − 1、无 below(落点视觉垫底)\n * 取 above + 1。priority 恒整数——wire 解码按 PHP intval 语义取整,小数在\n * 保存→打开时值漂移、往返恒等即破(工单 13)。两侧邻层至少存在其一(调用方\n * 保证 n ≥ 2 / 源层在场)。\n */\nfunction midpointPriority(below: number | null, above: number | null): number | null {\n if (below !== null && above !== null) {\n return below - above >= 2 ? Math.floor((below + above) / 2) : null\n }\n if (below !== null) return below - 1\n return above === null ? null : above + 1\n}\n\n/**\n * 全表归一化:按数组序(头=垫底)重赋带间隙的整数阶梯((N−1−i) × GAP),\n * 恢复整数间隔并为后续中点插值留余量;仅作「无整数间隙」时的兜底。\n */\nconst ROOT_PRIORITY_GAP = 1024\n\nfunction normalizeRootPriorities(layers: Draft<Layer>[]): void {\n for (let i = 0; i < layers.length; i += 1) {\n layers[i]!.priority = (layers.length - 1 - i) * ROOT_PRIORITY_GAP\n }\n}\n\n/**\n * 表格行重排(draft 原位变换):直接改 rows 数组序——容器内是嵌套数组序语义,\n * 行的 priority 不参与排序、不插值(与根层重排语义分立)。from/to 为行序号\n * (行 0 在视觉顶部),to 为「insert-before 原始序号」落点 ∈ [0, rows.length]。\n * 返回 (from, to)(to 为插入后最终下标,供路径重映射);null = 无变化/入参不合法。\n */\nexport function moveTableRowInDraft(\n draft: Draft<Canvas>,\n tablePath: LayerPath,\n fromRow: number,\n toRow: number,\n): { from: number; to: number } | null {\n const table = resolveLayer(draft, tablePath)\n if (!table || table.type !== 'TableLayer') return null\n const rows = table.rows as Draft<TableRowLayer>[]\n const reduced = moveGuard(rows.length, fromRow, toRow)\n if (reduced === null) return null\n\n const [moved] = rows.splice(fromRow, 1)\n rows.splice(reduced, 0, moved!)\n return { from: fromRow, to: reduced }\n}\n\n/**\n * 新增根层(draft 原位变换):工厂缺省形态 + priority = min − 1(空画布取 0),\n * push 到数组尾(视觉最上层)。返回新层的数组下标。textDefaults(text-defaults\n * 工单 01)可选透传工厂覆盖——session 侧 addRootLayer 传新层样式槽,其余调用\n * 方不传走纯工厂缺省。\n */\nexport function addRootLayerInDraft(draft: Draft<Canvas>, type: LayerType, textDefaults?: Partial<NextTextStyle>): number {\n return insertRootLayerInDraft(draft, createDefaultLayer(type, textDefaults) as Draft<Layer>)\n}\n\n/**\n * 根层插入(draft 原位变换,工单 14 剪贴板粘贴共用):priority = min − 1(空画布\n * 取 0)+ push 到数组尾(视觉最上层)。数组序保持 priority 降序的解码不变量\n * (min−1 严格小于现最小值)。调用方负责传入的图层形态(如粘贴子树的深拷贝 +\n * 位置偏移)。返回新层的数组下标。\n */\nexport function insertRootLayerInDraft(draft: Draft<Canvas>, layer: Draft<Layer>): number {\n let min = Number.POSITIVE_INFINITY\n for (const existing of draft.layers) min = Math.min(min, existing.priority)\n layer.priority = Number.isFinite(min) ? min - 1 : 0\n draft.layers.push(layer)\n return draft.layers.length - 1\n}\n\n/**\n * 根层紧邻插入(draft 原位变换,alt-drag-paste 工单 01 原语,⌘V 粘贴工单 02 共用):\n * 新层 splice 到源层后一格 = 源层视觉上方一格,priority 取源与其视觉上一层(数组\n * 序 +1 位)之间的中点插值——数组序全程保持 priority 降序的解码不变量,整数插值\n * 同 moveRootLayerInDraft 门规(midpointPriority:无整数间隙时全表归一化兜底);\n * 源为视觉最顶/数组尾时取 source.priority − 1(置顶 min−1 同门)。\n * source 按根下标或路径寻址:路径取根祖先段(path[1],格内容等子树源紧邻其根层)。\n * 返回新层的数组下标;源下标越界/路径无数字根段返回 null(退化语义归调用方——\n * ⌘V 的「源不可解析退化置顶」在消费面回落 insertRootLayerInDraft)。\n */\nexport function insertRootLayerAdjacentInDraft(\n draft: Draft<Canvas>,\n layer: Draft<Layer>,\n source: number | LayerPath,\n): number | null {\n const rootIndex = typeof source === 'number' ? source : source[1]\n if (typeof rootIndex !== 'number' || !Number.isSafeInteger(rootIndex)) return null\n const layers = draft.layers as Draft<Layer>[]\n if (rootIndex < 0 || rootIndex >= layers.length) return null\n\n // 落点两侧:below = 源层(数组左),above = 视觉上一层(数组右),缺 above 即\n // 源为视觉最顶/数组尾(midpointPriority 内退化为 source − 1)\n const sourcePriority = layers[rootIndex]!.priority\n const above = rootIndex + 1 < layers.length ? layers[rootIndex + 1]!.priority : null\n layers.splice(rootIndex + 1, 0, layer)\n const priority = midpointPriority(sourcePriority, above)\n if (priority !== null) {\n layer.priority = priority\n } else {\n normalizeRootPriorities(layers)\n }\n return rootIndex + 1\n}\n\n/** 删除定位:list = 容器列表 splice(roots/rows/cells),content = 格内容置 null */\nexport type DeletedLayerRef =\n | { kind: 'list'; containerPath: LayerPath; key: 'layers' | 'rows' | 'cells'; index: number }\n | { kind: 'content'; path: LayerPath }\n\n/**\n * 删除图层(draft 原位变换):根层连子树整删、行/格走列表 splice、格内容置 null\n * (镜像 impl 研究 §2.8「条目操作」)。路径越界/形态不符/内容已空返回 null(无操作)。\n */\nexport function deleteLayerInDraft(draft: Draft<Canvas>, path: LayerPath): DeletedLayerRef | null {\n if (!isLayerPath(path)) return null\n\n // 格内容:content 段无索引,父 cell 路径 = 去掉尾段\n if (path[path.length - 1] === 'content') {\n const cell = resolveLayer(draft, path.slice(0, -1))\n if (!cell || cell.type !== 'TableCellLayer' || cell.content === null) return null\n ;(cell as Draft<TableCellLayer>).content = null\n return { kind: 'content', path }\n }\n\n // 根层:路径 ['layers', i],尾段即数字下标\n if (path.length === 2) {\n const index = path[1]\n const layers = draft.layers as Draft<Layer>[]\n if (typeof index !== 'number' || index >= layers.length) return null\n layers.splice(index, 1)\n return { kind: 'list', containerPath: [], key: 'layers', index }\n }\n\n // 行/格:路径 (key, index) 成对收尾——尾段是下标,倒数第二段是键 'rows' | 'cells'\n const index = path[path.length - 1]\n const key = path[path.length - 2]\n if (typeof index !== 'number' || (key !== 'rows' && key !== 'cells')) return null\n\n if (key === 'rows') {\n const table = resolveLayer(draft, path.slice(0, -2))\n if (!table || table.type !== 'TableLayer' || index >= table.rows.length) return null\n ;(table.rows as Draft<TableRowLayer>[]).splice(index, 1)\n return { kind: 'list', containerPath: path.slice(0, -2), key: 'rows', index }\n }\n const row = resolveLayer(draft, path.slice(0, -2))\n // 两型 union(spec §3.2):模板格删除与 V1 格同走 cells splice;行模板替身\n // (template 收尾路径)尾段非数字在上方守卫天然拒绝\n if (!row || (row.type !== 'TableRowLayer' && row.type !== 'TableRowTemplate') || index >= row.cells.length) {\n return null\n }\n ;(row.cells as Draft<TableCellLayer>[]).splice(index, 1)\n return { kind: 'list', containerPath: path.slice(0, -2), key: 'cells', index }\n}\n","/**\n * 表格容器结构编辑的内核语义(工单 12)。\n *\n * 重建路径与 graph 解码同源:decode.ts 镜像的 addRow/addCell/addContentLayer\n * 容器副作用在这里以 draft 变换原语复现,编辑器的行/格增删移动一律经这些原语\n * 落库——「add 同步语义」与「打开 graph 的强制同步」由同一份规则描述:\n *\n * - addRow:行宽 := 表宽、autoWidth 关;\n * - addCell:行高取最高单元格(只增长不收缩),增长时固化(autoHeight 关);\n * - addContentLayer:内容宽 := 格宽、autoWidth 关;格 auto 高 → 采纳内容动态高\n * 并固化(autoHeight 关),格固定高 → 压平内容高并关内容 autoHeight。\n *\n * 解码在每次打开时重断言这些耦合(行宽=表宽、内容宽=格宽、固定格内容高=格高、\n * 行高 ≥ 最高格),因此编辑器的字段写入(updateSpec)在表格子树按同一组规则\n * 收口(canonicalizeTableSyncInDraft)——凡解码会强同步的耦合,写入后立即重断言,\n * encode→decode 往返恒等在任何编辑序列后不破;解码不强求的(行高收缩)同样\n * 不重算,「先 add 后改尺寸不重算」的既有语义在重建路径下成立。\n *\n * 带内容的格在解码后恒为固定高(auto 标志被采纳语义固化),因此格的 autoHeight\n * 切换不是裸标志写:开 = 采纳内容动态高(autowrap 文本即「行数×行高+padding」)\n * 并固化,空格 = 置标志(解码对无内容的格保留该标志,往返恒等不受影响)。\n *\n * 模板态(TableLayer V2):行模板与 rows XOR,行级结构编辑在模板态拒绝(镜像\n * PHP addRow 抛 InvalidArgumentException,编辑器取 no-op 返回 null);表宽写入\n * 对行模板做同款宽度重同步(镜像解码 setTemplate→setWidth)。模板创作 UX(spec\n * `.scratch/canvas-web-template-authoring/`):模板格的增删重排开放——加格镜像\n * PHP TableRowTemplate::addCell 的零高度耦合(ADR 0006),行模板本身不可增删,\n * 双向转换原语是唯一合法的模板态进出门;模板子树进大纲、替身路径可选中(推翻\n * 决策 2026-09,勘误见 canvas-web-table-v2 issues/02 Comments)。\n *\n * 全模块纯 draft 变换、无 DOM;与 layerPanel.ts(工单 10 根层/行重排)分立。\n */\nimport { layerHeight, lineHeightPx, textLines, type Canvas, type Layer, type TableCellLayer, type TableRowLayer, type TableRowTemplateLayer, type TextLayoutPolicies } from '@hankchen/canvas'\nimport type { Draft } from 'immer'\n\nimport { pathsEqual, resolveLayer, isTemplateSubtreePath, type LayerPath } from '../shared/layerPath'\nimport { createDefaultLayer, moveGuard } from './layerPanel'\n\ntype DraftRow = Draft<TableRowLayer>\ntype DraftCell = Draft<TableCellLayer>\ntype DraftContent = Draft<Layer>\n/** 行模板/具体行的 shape 写入面:syncRowWidth 只触 shape,两型结构兼容(工票 02) */\ntype DraftRowOrTemplate = Draft<TableRowLayer | TableRowTemplateLayer>\n\n/**\n * resolveLayer 的 draft 断言形态:immer draft 结构上兼容只读领域类型,navigate\n * 的下降逻辑同一套;返回值按 draft 断言以解除 readonly(写入侧专用)。\n */\nfunction resolveDraft(root: Draft<Canvas>, path: LayerPath): DraftContent | null {\n return resolveLayer(root, path) as DraftContent | null\n}\n\n/** 归一 -0(PHP 整数域无 -0) */\nfunction normZero(value: number): number {\n return value === 0 ? 0 : value\n}\n\n/**\n * 内容层的动态高(采纳语义的目标高度):带内容的格在规范文档里恒为固定高\n * (内容高已被压平、autoHeight 已固化),「采纳」必须按内容自身的排版参数重算\n * 动态高——TextLayer autowrap = 行数×行高+padding、单行 = 行高+padding(空文本\n * 只剩 padding);其余类型走 layerHeight(QR 按宽正方形、其余声明高)。\n */\nfunction contentDynamicHeight(content: DraftContent, policies?: TextLayoutPolicies): number {\n if (content.type === 'TextLayer') {\n const padHeight = normZero(Math.trunc(content.shape.padding.top + content.shape.padding.bottom))\n if (content.autowrap) {\n return lineHeightPx(content) * textLines(content, policies).length + padHeight\n }\n return content.text === '' ? padHeight : lineHeightPx(content) + padHeight\n }\n return layerHeight(content, policies)\n}\n\n/** addRow 副作用:行宽同步表宽并关 autoWidth(行模板重断言复用同款同步) */\nexport function syncRowWidthInDraft(row: DraftRowOrTemplate, tableWidth: number): void {\n row.shape.width = tableWidth\n row.shape.autoWidth = false\n}\n\n/**\n * 模板格内容的宽度同步(addTemplateContentLayer 镜像):只有宽度耦合,格与内容\n * 的声明高/autoHeight 原样保留(ADR 0006 高度耦合全豁免,decodeTemplateCellLayer\n * 同门)——与 V1 的压平/采纳语义分立。\n */\nfunction syncTemplateContentWidthInDraft(content: DraftContent, cell: DraftCell): void {\n content.shape.width = cell.shape.width\n content.shape.autoWidth = false\n}\n\n/** addCell 副作用:行高取最高单元格(只增长不收缩),增长时固化(关 autoHeight) */\nexport function growRowToCellInDraft(row: DraftRow, cellHeight: number): void {\n if (row.shape.height < cellHeight) {\n row.shape.height = cellHeight\n row.shape.autoHeight = false\n }\n}\n\n/**\n * addContentLayer 副作用:内容宽同步格宽并关 autoWidth;格 auto 高 → 采纳内容\n * 动态高并固化(关 autoHeight),格固定高 → 压平内容高并关内容 autoHeight。\n * layerHeight 消费文本策略注入缝(与绘制/命中同一套度量)。\n */\nexport function syncContentIntoCellInDraft(content: DraftContent, cell: DraftCell, policies?: TextLayoutPolicies): void {\n content.shape.width = cell.shape.width\n content.shape.autoWidth = false\n if (cell.shape.autoHeight) {\n cell.shape.height = contentDynamicHeight(content, policies)\n cell.shape.autoHeight = false\n } else {\n content.shape.height = cell.shape.height\n content.shape.autoHeight = false\n }\n}\n\n/** 行内最高单元格(layerHeight 语义,与解码 addCell 逐格比较同源);空行 0 */\nfunction maxCellHeight(row: DraftRow, policies?: TextLayoutPolicies): number {\n let max = 0\n for (const cell of row.cells) {\n const height = layerHeight(cell, policies)\n if (height > max) max = height\n }\n return max\n}\n\n/** 格所在行的「行高取最高格」维持(只增长不收缩);行路径悬空静默跳过 */\nfunction growRowOfCellInDraft(cellPath: LayerPath, cellHeight: number, root: Draft<Canvas>): void {\n const row = resolveDraft(root, cellPath.slice(0, -2))\n if (row && row.type === 'TableRowLayer') growRowToCellInDraft(row, cellHeight)\n}\n\n/** 缺省格 + 缺省文本内容的重建装配:格宽取末格宽(无格取 fallbackWidth)、内容同步、行高取最高格 */\nfunction buildDefaultCellIntoRowInDraft(row: DraftRow, fallbackWidth: number): DraftCell {\n const cells = row.cells as DraftCell[]\n const cell = createDefaultLayer('TableCellLayer') as DraftCell\n cell.shape.width = cells.length > 0 ? cells[cells.length - 1]!.shape.width : fallbackWidth\n const content = createDefaultLayer('TextLayer') as DraftContent\n syncContentIntoCellInDraft(content, cell)\n cell.content = content\n growRowToCellInDraft(row, cell.shape.height)\n return cell\n}\n\n/**\n * 新增行(draft 原位变换):缺省行 + 缺省格(带缺省文本内容)走完整重建路径——\n * 行宽=表宽(addRow)→ 内容宽=格宽、固定格压平内容高(addContentLayer)→\n * 行高取最高格(addCell)。首格宽取行宽(单格行铺满),后续格宽随末格(均齐\n * 直觉)。返回新行下标;路径无法解析到表返回 null。\n *\n * 模板态守卫(工票 02):目标表 template !== null 返回 null、文档零变化——\n * 镜像 PHP addRow 抛 InvalidArgumentException 的 API 误用语义,编辑器取 no-op\n * (防 template+rows 双键落 wire)。\n */\nexport function addTableRowInDraft(draft: Draft<Canvas>, tablePath: LayerPath): number | null {\n const table = resolveDraft(draft, tablePath)\n if (!table || table.type !== 'TableLayer') return null\n if (table.template !== null) return null\n const rows = table.rows as DraftRow[]\n\n const row = createDefaultLayer('TableRowLayer') as DraftRow\n syncRowWidthInDraft(row, table.shape.width)\n const cell = buildDefaultCellIntoRowInDraft(row, row.shape.width)\n row.cells = [cell]\n rows.push(row)\n return rows.length - 1\n}\n\n/**\n * 新增单元格(draft 原位变换):缺省格 + 缺省文本内容走重建路径(内容同步 + 行高\n * 取最高格),格宽取末格宽(首格取行宽)。返回新格下标;路径无法解析到行返回 null。\n */\nexport function addTableCellInDraft(draft: Draft<Canvas>, rowPath: LayerPath): number | null {\n const row = resolveDraft(draft, rowPath)\n if (!row || row.type !== 'TableRowLayer') return null\n const cells = row.cells as DraftCell[]\n const cell = buildDefaultCellIntoRowInDraft(row, row.shape.width)\n cells.push(cell)\n return cells.length - 1\n}\n\n/**\n * 模板行加格(draft 原位变换,spec §3.1):缺省格 + 缺省文本内容——格宽取末格\n * 宽(无格取模板行宽)、内容只做宽度同步(addTemplateContentLayer 镜像);格与\n * 内容的声明高/autoHeight 原样、模板行 shape 零触碰(ADR 0006 高度耦合全豁免,\n * 无任何行高耦合)。返回新格下标;路径非表/非模板态返回 null。\n */\nexport function addTemplateCellInDraft(draft: Draft<Canvas>, tablePath: LayerPath): number | null {\n const table = resolveDraft(draft, tablePath)\n if (!table || table.type !== 'TableLayer' || table.template === null) return null\n const template = table.template as Draft<TableRowTemplateLayer>\n const cells = template.cells as DraftCell[]\n const cell = createDefaultLayer('TableCellLayer') as DraftCell\n cell.shape.width = cells.length > 0 ? cells[cells.length - 1]!.shape.width : template.shape.width\n const content = createDefaultLayer('TextLayer') as DraftContent\n syncTemplateContentWidthInDraft(content, cell)\n cell.content = content\n cells.push(cell)\n return cells.length - 1\n}\n\n/**\n * V1→V2 转换(draft 原位变换,spec §2.3):末行深拷贝重标定为行模板——搬运规则\n * 为行宽 := 表宽关 autoWidth(解码 setTemplate 同门重断言,V1 行宽恒等表宽,数值\n * 不变),格宽/格高/内容 data 全部原样(V1 带内容格恒固定高,无 auto 标志残留,\n * 零特判);rows 清空(XOR)、rowsPath 落 data 域。rowsPath 空串拒绝(解码\n * rows_path_missing 硬约束的对齐兜底——创建表单校验前置,此处双保险)。返回\n * true;no-op(路径非表/已是模板态/空表/末行零格/rowsPath 空)返回 null。\n */\nexport function convertTableToTemplateInDraft(\n draft: Draft<Canvas>,\n tablePath: LayerPath,\n rowsPath: string,\n): boolean | null {\n const table = resolveDraft(draft, tablePath)\n if (!table || table.type !== 'TableLayer' || table.template !== null) return null\n if (rowsPath === '') return null\n const rows = table.rows as DraftRow[]\n const last = rows[rows.length - 1]\n if (last === undefined || last.cells.length === 0) return null\n\n // JSON 往返深拷贝(与剪贴板 cloneLayerSubtree 同式,draft 节点落为普通对象)\n const template = JSON.parse(JSON.stringify(last)) as Draft<TableRowTemplateLayer>\n template.type = 'TableRowTemplate'\n syncRowWidthInDraft(template, table.shape.width)\n\n table.rows = []\n table.template = template\n table.rowsPath = rowsPath\n return true\n}\n\n/**\n * V2→V1 逆向转换(draft 原位变换,spec §2.3):模板深拷贝重标定为单行——内容\n * data 原样(expression 标记保留,V1 字面渲染既有行为),随后按解码副作用重断言\n * V1 归一(decodeTableRowLayer/decodeTableCellLayer 同门,往返恒等要求):带内容\n * 格 auto → 采纳内容动态高并固化、固定格压平内容高(addContentLayer 语义);空\n * auto 格声明高归零(auto 标志解码归一);行高取最高格增长固化(addCell 语义,\n * 只增长不收缩)。template 置 null、rowsPath 清空(V1 不消费)。返回 true;\n * no-op(路径非表/非模板态)返回 null。\n */\nexport function convertTableToRowsInDraft(\n draft: Draft<Canvas>,\n tablePath: LayerPath,\n policies?: TextLayoutPolicies,\n): boolean | null {\n const table = resolveDraft(draft, tablePath)\n if (!table || table.type !== 'TableLayer' || table.template === null) return null\n\n const row = JSON.parse(JSON.stringify(table.template)) as DraftRow\n row.type = 'TableRowLayer'\n syncRowWidthInDraft(row, table.shape.width)\n for (const cell of row.cells as DraftCell[]) {\n if (cell.content !== null) {\n // 镜像 setCellAutoHeightInDraft 的两步归一:auto 格先采纳内容动态高并\n // 固化,再按固定格压平内容高(解码对 V1 带内容格恒归一,往返恒等要求)\n if (cell.shape.autoHeight) {\n cell.shape.height = contentDynamicHeight(cell.content, policies)\n cell.shape.autoHeight = false\n }\n syncContentIntoCellInDraft(cell.content, cell, policies)\n } else if (cell.shape.autoHeight) {\n cell.shape.height = 0\n }\n }\n if (row.shape.autoHeight) row.shape.height = 0\n growRowToCellInDraft(row, maxCellHeight(row, policies))\n\n table.template = null\n table.rowsPath = ''\n table.rows = [row]\n return true\n}\n\n/** 跨容器移动的重映射凭据:源容器纯删除 + 目标容器纯插入(from = 原长、to = 落点) */\nexport interface MovedSubtreeRef {\n /** 移动前子树根路径(选择/悬停重挂基准) */\n fromPath: LayerPath\n /** 移动后子树根路径 */\n toPath: LayerPath\n source: { containerPath: LayerPath; key: 'rows' | 'cells'; from: number; to: number }\n target: { containerPath: LayerPath; key: 'rows' | 'cells'; from: number; to: number }\n}\n\n/**\n * 同行格重排(draft 原位变换):直接改 cells 数组序——容器内嵌套数组序语义,格的\n * priority 不参与(与 moveTableRow 同款 moveGuard 语义)。from/to 为格序号(格 0\n * 在视觉左端),to 为「insert-before 原始序号」落点 ∈ [0, cells.length]。返回\n * (from, to)(to 为插入后最终下标,供路径重映射);null = 无变化/入参不合法。\n */\nexport function moveTableCellInDraft(\n draft: Draft<Canvas>,\n rowPath: LayerPath,\n fromCell: number,\n toCell: number,\n): { from: number; to: number } | null {\n const row = resolveDraft(draft, rowPath)\n // 两型 union(spec §3.2):模板格同行重排复用同一原语(模板仅一行,跨行不可达)\n if (!row || (row.type !== 'TableRowLayer' && row.type !== 'TableRowTemplate')) return null\n const cells = row.cells as DraftCell[]\n const reduced = moveGuard(cells.length, fromCell, toCell)\n if (reduced === null) return null\n\n const [moved] = cells.splice(fromCell, 1)\n cells.splice(reduced, 0, moved!)\n return { from: fromCell, to: reduced }\n}\n\n/**\n * 行跨表移动(draft 原位变换):从源表摘除、插入目标表 toIndex 处,addRow 副作用\n * 复现(行宽同步目标表宽)。toIndex ∈ [0, 目标 rows.length](insert-before 落点,\n * 不需摘除调整——目标容器不含被移行)。同表重排返回 null(moveTableRow 的\n * moveGuard 语义,调用方分流)。\n */\nexport function moveTableRowToTableInDraft(\n draft: Draft<Canvas>,\n rowPath: LayerPath,\n targetTablePath: LayerPath,\n toIndex: number,\n): MovedSubtreeRef | null {\n const sourceTablePath = rowPath.slice(0, -2)\n if (pathsEqual(sourceTablePath, targetTablePath)) return null\n const from = rowPath[rowPath.length - 1]\n if (typeof from !== 'number') return null\n const sourceTable = resolveDraft(draft, sourceTablePath)\n const targetTable = resolveDraft(draft, targetTablePath)\n if (!sourceTable || !targetTable || sourceTable.type !== 'TableLayer' || targetTable.type !== 'TableLayer') {\n return null\n }\n // 目标表模板态守卫(工票 02):移入即落 template+rows 双键,拒绝(no-op null);\n // 源表模板态 rows 恒空,下方下标守卫天然挡住,无需显式分支\n if (targetTable.template !== null) return null\n if (!Number.isSafeInteger(toIndex) || toIndex < 0 || toIndex > targetTable.rows.length) return null\n\n const sourceRows = sourceTable.rows as DraftRow[]\n if (from >= sourceRows.length) return null\n const [movedRaw] = sourceRows.splice(from, 1)\n const moved = movedRaw as DraftRow\n syncRowWidthInDraft(moved, targetTable.shape.width)\n\n const targetRows = targetTable.rows as DraftRow[]\n const finalTo = Math.min(toIndex, targetRows.length)\n targetRows.splice(finalTo, 0, moved)\n\n return {\n fromPath: rowPath,\n toPath: [...targetTablePath, 'rows', finalTo],\n source: { containerPath: sourceTablePath, key: 'rows', from, to: from },\n // 目标容器是纯插入:remap 的 from 取插入前长度(该长度不受源删除影响)\n target: { containerPath: targetTablePath, key: 'rows', from: targetRows.length - 1, to: finalTo },\n }\n}\n\n/**\n * 格跨行移动(draft 原位变换):从源行摘除、插入目标行 toIndex 处,addCell 副作用\n * 复现(目标行高取最高格)+ addContentLayer 副作用重断言(内容层归属随格,宽/高\n * 同步幂等)。toIndex ∈ [0, 目标 cells.length](insert-before 落点,不需摘除调整)。\n * 同行重排返回 null(moveTableCellInDraft 的 moveGuard 语义,调用方分流)。\n */\nexport function moveTableCellToRowInDraft(\n draft: Draft<Canvas>,\n cellPath: LayerPath,\n targetRowPath: LayerPath,\n toIndex: number,\n policies?: TextLayoutPolicies,\n): MovedSubtreeRef | null {\n const sourceRowPath = cellPath.slice(0, -2)\n if (pathsEqual(sourceRowPath, targetRowPath)) return null\n const from = cellPath[cellPath.length - 1]\n if (typeof from !== 'number') return null\n const sourceRow = resolveDraft(draft, sourceRowPath)\n const targetRow = resolveDraft(draft, targetRowPath)\n if (!sourceRow || !targetRow || sourceRow.type !== 'TableRowLayer' || targetRow.type !== 'TableRowLayer') {\n return null\n }\n if (!Number.isSafeInteger(toIndex) || toIndex < 0 || toIndex > targetRow.cells.length) return null\n\n const sourceCells = sourceRow.cells as DraftCell[]\n if (from >= sourceCells.length) return null\n const [movedRaw] = sourceCells.splice(from, 1)\n const moved = movedRaw as DraftCell\n growRowToCellInDraft(targetRow, moved.shape.height)\n if (moved.content !== null) {\n syncContentIntoCellInDraft(moved.content, moved, policies)\n }\n\n const targetCells = targetRow.cells as DraftCell[]\n const finalTo = Math.min(toIndex, targetCells.length)\n targetCells.splice(finalTo, 0, moved)\n\n return {\n fromPath: cellPath,\n toPath: [...targetRowPath, 'cells', finalTo],\n source: { containerPath: sourceRowPath, key: 'cells', from, to: from },\n target: { containerPath: targetRowPath, key: 'cells', from: targetCells.length - 1, to: finalTo },\n }\n}\n\n/**\n * 格 autoHeight 切换(draft 原位变换):开且带内容 = 采纳内容动态高并固化\n * (解码对带内容的格恒归一为固定高,标志保留会破坏往返恒等);开且空格 = 置\n * 标志并把声明高归零(解码对 auto 标志恒清零声明高,PHP setHeight('auto') 同门);\n * 关 = 清标志。采纳可能增高,行高取最高格的解码不变量随之维持(只增长不收缩)。\n * 路径无法解析到格返回 false。\n */\nexport function setCellAutoHeightInDraft(\n draft: Draft<Canvas>,\n cellPath: LayerPath,\n on: boolean,\n policies?: TextLayoutPolicies,\n): boolean {\n const cell = resolveDraft(draft, cellPath)\n if (!cell || cell.type !== 'TableCellLayer') return false\n\n if (on && cell.content !== null) {\n cell.shape.height = contentDynamicHeight(cell.content, policies)\n cell.shape.autoHeight = false\n // 固定格归一:内容高随采纳后的格高压平(解码强同步,往返恒等要求)\n syncContentIntoCellInDraft(cell.content, cell, policies)\n } else if (on) {\n cell.shape.height = 0\n cell.shape.autoHeight = true\n } else {\n cell.shape.autoHeight = false\n }\n\n growRowOfCellInDraft(cellPath, cell.shape.height, draft)\n return true\n}\n\n/**\n * 字段写入后的解码不变量重断言:解码在每次打开时强同步表格耦合,编辑器写入后\n * 立即重断言同一组关系(往返恒等的编辑器侧镜像)——\n * - 表:shape.width/autoWidth 写入 → 全部行宽重同步(行宽=表宽);模板态对模板\n * 行做同款重同步(镜像解码 setTemplate→setWidth,工票 02);\n * - 行:行宽恒归表宽(强同步字段的写入被覆写)、行高只增长到最高格;\n * - 格:内容宽/高按 addContentLayer 重同步(auto 采纳 / 固定压平)、行高取最高格;\n * - 内容:归属格的同步重断言(隐藏字段的直写被压回)。\n * key = updateSpec 的图层内字段路径;非表格子树路径原样返回(零 patch)。\n */\nexport function canonicalizeTableSyncInDraft(\n draft: Draft<Canvas>,\n path: LayerPath,\n key: readonly string[],\n policies?: TextLayoutPolicies,\n): void {\n // 内容层:尾段 'content',归属格 = 去掉尾段\n if (path[path.length - 1] === 'content') {\n const cellPath = path.slice(0, -1)\n const cell = resolveDraft(draft, cellPath)\n if (!cell || cell.type !== 'TableCellLayer' || cell.content === null) return\n // 模板格内容(决策 2026-09):宽度耦合沿用、高度耦合全豁免(行高定稿\n // 留给展开,无「行高取最高格」可重断言)\n if (isTemplateSubtreePath(path)) {\n syncTemplateContentWidthInDraft(cell.content, cell)\n return\n }\n syncContentIntoCellInDraft(cell.content, cell, policies)\n growRowOfCellInDraft(cellPath, cell.shape.height, draft)\n return\n }\n\n const containerKey = path[path.length - 2]\n if (containerKey === 'rows') {\n const row = resolveDraft(draft, path)\n if (!row || row.type !== 'TableRowLayer') return\n const table = resolveDraft(draft, path.slice(0, -2))\n if (table && table.type === 'TableLayer') syncRowWidthInDraft(row, table.shape.width)\n // auto 标志的解码归一:声明高归零(在增长判定之前——增长会合法覆写高度并固化)\n if (row.shape.autoHeight) row.shape.height = 0\n growRowToCellInDraft(row, maxCellHeight(row, policies))\n return\n }\n\n if (containerKey === 'cells') {\n const cell = resolveDraft(draft, path)\n if (!cell || cell.type !== 'TableCellLayer') return\n // 模板格(决策 2026-09):内容宽度同步照做,行高耦合全豁免\n if (isTemplateSubtreePath(path)) {\n if (cell.content !== null) syncTemplateContentWidthInDraft(cell.content, cell)\n return\n }\n if (cell.content !== null) syncContentIntoCellInDraft(cell.content, cell, policies)\n else if (cell.shape.autoHeight) cell.shape.height = 0\n growRowOfCellInDraft(path, cell.shape.height, draft)\n return\n }\n\n // 根层表:仅 shape.width/autoWidth 写入联动行宽(其余字段与行无耦合)\n if (path[0] === 'layers' && typeof path[1] === 'number' && key[0] === 'shape' && (key[1] === 'width' || key[1] === 'autoWidth')) {\n const layer = draft.layers[path[1]] as DraftContent | undefined\n if (!layer || layer.type !== 'TableLayer') return\n if (layer.shape.autoHeight) layer.shape.height = 0\n // 模板态重同步(工票 02):镜像解码 setTemplate→setWidth 的宽度耦合,保\n // encode→decode 在编辑序列后稳定;模板态 rows 恒空,下方循环天然空转\n if (layer.template !== null) syncRowWidthInDraft(layer.template, layer.shape.width)\n for (const row of layer.rows as DraftRow[]) syncRowWidthInDraft(row, layer.shape.width)\n }\n}\n","/**\n * headless 编辑器内核的 observable store。\n *\n * - doc 分支:解码后的领域画布(@hankchen/canvas 的 Canvas),唯一事实源。\n * 写入口唯一:transact —— immer produceWithPatches 产物浅替换 + patch 广播。\n * 历史(工单 08 双栈):mergeKey 相同的连续事务合并为一步(一次拖动 = 一步\n * 历史),pointerup 后 closeMerge 闭合;undo/redo 手写双栈回放 patch,\n * undo 后新事务改写 redo 栈(Figma 语义),上限 100 步、不跨会话。\n * patch 本身可 JSON 序列化(协作的将来之路,v1 不做协作)。\n * - ui 分支:视口/选择/悬停/拖动会话等易变状态,整体替换、**永不进历史**\n * (红线 3 的编辑器延伸:派生状态只住这里,不写文档)。\n * - 通知携带变更位置(scope/branch)与 patch 组,绑定层与渲染调度据此细分脏区。\n */\nimport { applyPatches, enablePatches, produceWithPatches, setAutoFreeze, type Draft, type Patch } from 'immer'\n\nimport type { Canvas, LayerBox, LayerType } from '@hankchen/canvas'\n\nimport type { Point, Rect, Viewport } from '../spatial/camera'\nimport type { Guide, SnapAxis } from '../spatial/snap'\nimport type { ResizeHandle } from '../spatial/resize'\nimport { pathsEqual, type LayerPath } from '../shared/layerPath'\nimport type { ExpressionSchemaNode } from '../shared/expressionSchema'\nimport type { ResourceStatusMap } from '../shared/resourceStatus'\nimport { defaultNextTextStyle, type NextTextStyle } from '../editing/layerPanel'\n\nenablePatches()\n// 关闭 immer 自动冻结:文档树以「不可变 + 结构共享」语义流转(引用相等即未变),\n// 该语义不依赖冻结;而绑定层(Vue)的响应式系统会把宿主对象封进 Proxy,冻结树\n// 一旦被代理封装,immer 后续 draft/freeze 读取子属性即触发 V8 Proxy 不变量报错\n// (工单 09 属性面板实测:autoFreeze 开启时面板提交必现 \"get on proxy\" 崩溃)。\n// 防误改由唯一写入口 transact 的纪律保证。\nsetAutoFreeze(false)\n\n/** 拖动会话(ui 分支):目标路径 + 起点场景坐标 + 起始 position 偏移 + 起始盒 */\nexport interface DragGesture {\n path: LayerPath\n startScene: Point\n startPosition: { x: number; y: number }\n /**\n * 拖动开始时的绝对盒(吸附求位基准,工单 01):盒坐标对 position 线性,\n * 暂定盒 = startBox + 本步位移;住会话态,文档零接触。\n */\n startBox: LayerBox\n /**\n * Alt+拖快速复制(alt-drag-paste 工单 01):beginDrag 起手一次性判定(源可\n * 复制才置位,缺省/undefined = 普通拖动)。会话内定死——中途无修饰键读数,\n * 松/按 Alt 均不影响(spec 决策 2,与「手势模式 pointerdown 一次性定死」同构)。\n */\n copy?: boolean\n /**\n * copyMode 副本尚未插入(首移未越过死区):drag.path 仍是源层、源层不动;\n * 首移越阈(ALT_DRAG_DEAD_ZONE_SCREEN_PX)单事务插入副本后置 false,path/\n * startPosition 重指向副本(换基准)。普通拖动恒 undefined。\n */\n copyPending?: boolean\n}\n\n/**\n * 缩放会话(ui 分支,工单 07):目标路径 + 八柄 + 起点场景坐标 + 起始解析盒\n * (吸附求位与几何基准,spatial/resize 消费)。会话对象住 ui 分支,文档只收\n * resizeTo 的合并事务;与 drag 会话互斥(手势起点一次性定死)。\n */\nexport interface ResizeGesture {\n path: LayerPath\n handle: ResizeHandle\n startScene: Point\n /**\n * 缩放开始时的绝对解析盒(几何/吸附基准):对缘固定、移动缘 = 起始缘 +\n * 位移;auto 标志采纳把本盒解析尺寸落地为声明值(首个位移事务)。\n */\n startBox: LayerBox\n}\n\n/**\n * 画拉建层橡皮筋会话(ui 分支,drag-create 工单 01):武装层型 + 起点场景坐标\n * + 当前点 + 橡皮筋矩形。拖拽期纯视觉——文档零变更(spec 决策 4,比先建后\n * resize 少踩零尺寸渲染/命中两坑),rect 是 createTo 求位产物(两点正规化 +\n * QR 钳方 + 吸附修正并入),overlay 直读绘制虚线矩形与 W×H 气泡;落库在\n * endCreate 单事务(返回新层路径),与 drag/resize 会话互斥。\n */\nexport interface CreateGesture {\n /** 本次画拉的层型(beginLayerCreate 时从 armedCreate 捕获,会话内定死) */\n type: LayerType\n /** 起点场景坐标(两点正规化与死区判定基准) */\n startScene: Point\n /** 当前指针场景坐标 */\n currentScene: Point\n /** 当前橡皮筋矩形(吸附修正已并入),绑定层 overlay 直读 */\n rect: Rect\n}\n\n/**\n * 文本编辑会话(ui 分支,工单 11):编辑中的文本层路径。live 文本住在绑定层的\n * textarea(非受控),提交经 commitTextEdit 一次性落文档——「每个拼音音节一个\n * undo」被会话缓冲天然避免(impl 研究 §2.4)。\n *\n * expression 是内容类型会话标志(canvas-web-expression-editing 工单 01,spec 决策\n * 2):beginTextEdit 按进入瞬间 `layer.expression !== null` 锚定的会话快照(与\n * verticalAnchorY「进入时计、编辑中稳定」同门),裁决提交去向(表达式会话含合法\n * 片段走标记写,字面会话恒字面写)与补全 enabled。住 ui 分支不进历史;面板编辑\n * 中改动 layer.expression 不回写标志(打标是显式动作,快照是提交去向的裁决依据);\n * 内容类型 pill 切换仅翻转本标志(零文档变更零历史步)。\n */\nexport interface TextEditingSession {\n path: LayerPath\n expression: boolean\n}\n\n/**\n * 查找替换会话(canvas-web-find-replace 工单 01,CONTEXT「查找替换」词条):\n * 面板开合(open)与查找/替换两词 + 命中游标(派生命中列表的偏移下标)。匹配\n * 列表派生不驻留——每次从 doc + query 现算,游标越界在读侧钳位;同文档内\n * 关开(closeFind→beginFind)保留查询词与游标。会话态族——不进历史、不写\n * graph;openDocument 换文档整体重置(换文档带旧查询词开面板反而怪)。\n */\nexport interface FindSession {\n open: boolean\n query: string\n replacement: string\n cursor: number\n}\n\n/** 查找会话缺省态(初始与 openDocument 重置同源) */\nconst initialFindSession = (): FindSession => ({ open: false, query: '', replacement: '', cursor: 0 })\n\nexport interface EditorUi {\n viewport: Viewport\n /** 当前选中图层路径(数组路径,patch path 前缀);null = 无选择 */\n selection: LayerPath | null\n /** 悬停图层路径(gizmo hover 高亮);null = 无悬停 */\n hovered: LayerPath | null\n /** 进行中的拖动会话;null = 无拖动 */\n drag: DragGesture | null\n /** 进行中的缩放会话(工单 07 八柄);null = 无缩放 */\n resize: ResizeGesture | null\n /**\n * 画拉建层武装态(drag-create 工单 01,CONTEXT「武装」词条):面板新增项/\n * 层型快捷键选定的一次性待命层型——下一次画布按下即开画拉,建层或 Esc 即\n * 解除。无粘性工具态(spec 决策 2);openDocument 重置。\n */\n armedCreate: LayerType | null\n /** 进行中的画拉建层橡皮筋会话(拖拽期纯视觉,文档零变更);null = 无 */\n create: CreateGesture | null\n /** 进行中的文本编辑会话;null = 非编辑态 */\n editing: TextEditingSession | null\n /**\n * 重命名编辑会话(layer-panel-ux 工单 09):正在行内改名的根层路径。与\n * editing 同款会话语义——住 ui 分支不进历史,提交经 commitRename 漏斗一次\n * 落文档;null = 非重命名态。\n */\n renaming: LayerPath | null\n /**\n * 属性面板锚点折叠区开合(layer-panel-ux 工票 03):默认收起,会话内记忆\n * (与 viewport 同款的面板偏好——openDocument 换文档不重置,永不进历史)。\n */\n anchorExpanded: boolean\n /**\n * 新层样式槽(text-defaults 工单 01,CONTEXT「新层样式」词条):记忆「下一\n * 个新建文本层用什么文本样式族」的编辑器级偏好——属性面板对 TextLayer 文本\n * 族字段的权威写(updateSpec 捕获)更新槽,建层(endCreate/addRootLayer)\n * 以槽值覆盖工厂缺省。与锚点折叠同款的面板偏好——openDocument 换文档不重\n * 置,永不进历史;随工程数据 prefs 桶跨会话持久(宿主通道,内核零 localStorage)。\n */\n nextTextStyle: NextTextStyle\n /**\n * 数据源 schema 声明(content-completion 工单 03/08,D2 宿主随会话注入):\n * 编译产物形状树或降级 null(根级结构性拒绝,注入期经\n * normalizeExpressionSchemaSource 一次性收口 + console 警告;局部故障不拒绝,\n * 该节点降叶子 + 注入期汇总告警一次,树其余照常服务)。只住会话态——\n * 不进 graph、不落 localStorage、不进 wire(红线 3 延伸);openDocument 换\n * 文档不重置(声明随会话,重注入/清除走同一入口)。\n */\n dataSourceSchema: ExpressionSchemaNode | null\n /**\n * 参考线(ruler-guides-snap 工单 01,ADR 0012):使用者从标尺拖出的会话级\n * 对位轴(水平/垂直、场景坐标)。零契约面——不进历史、不写 graph、保存零\n * 改动;openDocument 换文档重置(当次编辑会话语义)。\n */\n guides: readonly Guide[]\n /**\n * 标尺显隐(ruler-guides-snap 工单 01):缺省常显,⇧R 经会话 toggleRulers\n * 翻转。与视口同款的面板偏好——openDocument 换文档保留,永不进历史。\n */\n rulersVisible: boolean\n /**\n * 当次拖动命中的吸附轴(ruler-guides-snap 工单 01):dragTo 求位的副产物,\n * 供 canvas 域画贯穿吸附线——瞬时回显,松手即清空(endDrag),非拖动态为空。\n */\n snapAxes: readonly SnapAxis[]\n /**\n * 锁定根层路径集合(canvas-web-layer-lock 工单 01):会话级锁定态——锁定的\n * 根层整子树退出画布命中面,变更类动作(拖动起点/删除/微调——微调已随\n * kbd-nav 工单 02 接入同一谓词)在内核空转,属性编辑/显隐/改名/z 序等刻意通道\n * 不受限。零契约面:不进历史、不写 graph、wire 零键、渲染产物不变\n * (红线 3 同门);openDocument 换文档重置(guides 同门——锁定是文档内容\n * 防护非面板偏好,跨文档路径悬空会误锁他人)。锁定恒为根层路径,子树\n * 命中按前缀判定收口 isLockedPath。\n */\n lockedPaths: readonly LayerPath[]\n /**\n * 查找替换会话(canvas-web-find-replace 工单 01):面板开合 + 两词 + 游标的\n * 会话态族——不进历史、不写 graph,openDocument 换文档重置(guides 同门)。\n * 命中列表不在此驻留:由 editing 域扫描纯函数从 doc + query 派生(EditorSession\n * listFindMatches 查询口),overlay 高亮与查找条计数同源消费。\n */\n find: FindSession\n /**\n * 资源物化状态切片(placeholder-padding-hint 工单 02):key = 图片 src 原串、\n * value = 物化三态,宿主从渲染端物化状态机桥接注入(整体替换,散列键反解在\n * 宿主桥)。占位态判定的物化维度事实源——只住会话态:不进历史、不写 graph、\n * wire 零键(红线 3 延伸,物化状态机切片的 ui 分支落点);openDocument 换\n * 文档重置(资源态描述当次文档,跨文档旧 src 条目无意义)。\n */\n resourceStatuses: ResourceStatusMap\n}\n\n/** 一步历史:一次(或同键合并的多次)文档事务的正向/逆向 patch 组 */\nexport interface HistoryStep {\n /** 事务合并键(拖动/滑杆);null = 已闭合、不再合并 */\n mergeKey: string | null\n patches: Patch[]\n inversePatches: Patch[]\n}\n\nexport type EditorChange =\n | { scope: 'doc'; patches: Patch[]; inversePatches: Patch[] }\n | { scope: 'ui'; branch: keyof EditorUi }\n\n/**\n * doc 通知契约注记(canvas-web-layer-lock 工单 03 目验同族三处收口):openDocument\n * 整体重建 uiValue(selection/hovered/renaming/lockedPaths 等重置)但只发 doc 通知、\n * 不逐分支发 ui 通知——订阅侧凡镜像了会被 openDocument 重置的 ui 分支,必须在 doc\n * 通知里一并重读(useSelection 先例,useLayerPanel/usePropertyPanel 同门)。\n */\n\n/** undo 栈深度上限:更旧的事务被丢弃(不跨会话,会话内也只回溯有限步) */\nexport const MAX_HISTORY_STEPS = 100\n\ntype Listener = (change: EditorChange) => void\n\n/** 文档事务:收到 doc 的 immer draft,原位改字段即可 */\nexport type DocRecipe = (draft: Draft<Canvas>) => void\n\nexport interface TransactOptions {\n /** 同键连续事务合并为一步历史(拖动的每次 pointermove 传同键) */\n mergeKey?: string\n}\n\n/** 命中吸附轴内容等(拖动高频路径的短路比较,轴数至多 2) */\nfunction snapAxesEqual(a: readonly SnapAxis[], b: readonly SnapAxis[]): boolean {\n if (a === b) return true\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i += 1) {\n const x = a[i]!\n const y = b[i]!\n if (x.orientation !== y.orientation || x.position !== y.position || x.source !== y.source) return false\n }\n return true\n}\n\n/** 锁定集合内容等(逐路径值等,toggleLayerLock/重映射路径的短路比较) */\nfunction lockedPathsEqual(a: readonly LayerPath[], b: readonly LayerPath[]): boolean {\n if (a === b) return true\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i += 1) {\n if (!pathsEqual(a[i]!, b[i]!)) return false\n }\n return true\n}\n\n/** 资源物化状态内容等(逐键值等,宿主桥整体替换的短路比较——键序无关) */\nfunction resourceStatusesEqual(a: ResourceStatusMap, b: ResourceStatusMap): boolean {\n if (a === b) return true\n const aKeys = Object.keys(a)\n if (aKeys.length !== Object.keys(b).length) return false\n for (const key of aKeys) {\n if (a[key] !== b[key]) return false\n }\n return true\n}\n\n/** 新层样式内容等(五字段值等,捕获/预设写槽的短路比较) */\nfunction nextTextStyleEqual(a: NextTextStyle, b: NextTextStyle): boolean {\n return (\n a.font === b.font\n && a.fontSize === b.fontSize\n && a.fontColor === b.fontColor\n && a.autowrap === b.autowrap\n && a.lineHeight === b.lineHeight\n )\n}\n\nexport class EditorStore {\n private docValue: Canvas | null = null\n private uiValue: EditorUi = {\n viewport: { x: 0, y: 0, zoom: 1 },\n selection: null,\n hovered: null,\n drag: null,\n resize: null,\n armedCreate: null,\n create: null,\n editing: null,\n renaming: null,\n anchorExpanded: false,\n nextTextStyle: defaultNextTextStyle(),\n dataSourceSchema: null,\n guides: [],\n rulersVisible: true,\n snapAxes: [],\n lockedPaths: [],\n find: initialFindSession(),\n resourceStatuses: {},\n }\n /** undo 栈:已提交步,栈尾最新 */\n private undoSteps: HistoryStep[] = []\n /** redo 栈:被撤销步,栈尾最近一次撤销;transact 落新事务时整体改写 */\n private redoSteps: HistoryStep[] = []\n private readonly listeners = new Set<Listener>()\n\n get doc(): Canvas | null {\n return this.docValue\n }\n\n get ui(): EditorUi {\n return this.uiValue\n }\n\n /** 已提交步(undo 栈,栈尾最新);可撤销性走 canUndo */\n get history(): readonly HistoryStep[] {\n return this.undoSteps\n }\n\n get canUndo(): boolean {\n return this.undoSteps.length > 0\n }\n\n get canRedo(): boolean {\n return this.redoSteps.length > 0\n }\n\n /** 打开/替换文档:ui 选择/编辑/重命名会话与双向历史一并重置(新文档不继承旧路径/旧事务);\n * 参考线/锁定集合/查找会话/建层武装与画拉会话随当次会话清空(对位轴/文档内容防护/\n * 查询词/待命层型不跨文档),标尺显隐/锚点折叠/新层样式作为偏好保留 */\n openDocument(canvas: Canvas): void {\n this.docValue = canvas\n this.undoSteps = []\n this.redoSteps = []\n this.uiValue = {\n ...this.uiValue,\n selection: null,\n hovered: null,\n drag: null,\n resize: null,\n armedCreate: null,\n create: null,\n editing: null,\n renaming: null,\n guides: [],\n snapAxes: [],\n lockedPaths: [],\n find: initialFindSession(),\n resourceStatuses: {},\n }\n this.notify({ scope: 'doc', patches: [], inversePatches: [] })\n }\n\n /**\n * 文档事务唯一入口:produceWithPatches 求新树,无变化即空转(不进历史不通知);\n * 有变化则浅替换 doc、按 mergeKey 合并或追加 undo 栈(超限丢最旧步)、\n * 改写 redo 栈(undo 后的新事务弃用被撤销分支,Figma 语义)、广播 patch 组。\n */\n transact(recipe: DocRecipe, options: TransactOptions = {}): void {\n if (this.docValue === null) return\n const [next, patches, inversePatches] = produceWithPatches(this.docValue, recipe)\n if (patches.length === 0) return\n\n this.redoSteps = []\n const mergeKey = options.mergeKey ?? null\n const last = this.undoSteps[this.undoSteps.length - 1]\n if (mergeKey !== null && last !== undefined && last.mergeKey === mergeKey) {\n // 正向依序拼接;逆向要后事务先撤,故新组插在前\n this.undoSteps[this.undoSteps.length - 1] = {\n mergeKey,\n patches: [...last.patches, ...patches],\n inversePatches: [...inversePatches, ...last.inversePatches],\n }\n } else {\n this.undoSteps.push({ mergeKey, patches, inversePatches })\n if (this.undoSteps.length > MAX_HISTORY_STEPS) this.undoSteps.shift()\n }\n\n this.docValue = next\n this.notify({ scope: 'doc', patches, inversePatches })\n }\n\n /** 撤销最近一步:逆向 patch 回放,该步整体移入 redo 栈 */\n undo(): void {\n const step = this.undoSteps[this.undoSteps.length - 1]\n if (step === undefined || this.docValue === null) return\n this.undoSteps.pop()\n this.redoSteps.push(step)\n // 历史导航打断 mergeKey 会话:新顶步闭合,同键后续事务另起一步\n this.closeMerge()\n this.docValue = applyPatches(this.docValue, step.inversePatches)\n // 通知契约与 transact 对齐:patches = 本次生效变更,inversePatches = 再撤本次所需\n this.notify({ scope: 'doc', patches: step.inversePatches, inversePatches: step.patches })\n }\n\n /** 重做最近一次撤销:正向 patch 回放,该步移回 undo 栈 */\n redo(): void {\n const step = this.redoSteps[this.redoSteps.length - 1]\n if (step === undefined || this.docValue === null) return\n this.redoSteps.pop()\n this.undoSteps.push(step)\n this.closeMerge()\n this.docValue = applyPatches(this.docValue, step.patches)\n this.notify({ scope: 'doc', patches: step.patches, inversePatches: step.inversePatches })\n }\n\n /**\n * 闭合拖动/滑杆事务(pointerup、blur 提交,undo/redo 导航同理):最后一步的\n * mergeKey 清空后,同键的后续事务不再并入。给键名时只在键匹配时闭合\n * (防误伤其它键的开放步)。\n */\n closeMerge(mergeKey?: string): void {\n const last = this.undoSteps[this.undoSteps.length - 1]\n if (last === undefined || last.mergeKey === null) return\n if (mergeKey !== undefined && last.mergeKey !== mergeKey) return\n this.undoSteps[this.undoSteps.length - 1] = { ...last, mergeKey: null }\n }\n\n /** 视口更新:ui 分支整体替换,旧切片引用保持原值(computed 引用短路依赖此语义) */\n setViewport(viewport: Viewport): void {\n this.uiValue = { ...this.uiValue, viewport }\n this.notify({ scope: 'ui', branch: 'viewport' })\n }\n\n /** 选中:值等短路(重复点选同一层不重绘) */\n setSelection(path: LayerPath | null): void {\n this.setUiPath('selection', path)\n }\n\n /** 悬停:值等短路(指针在同一层内移动不重绘覆盖层) */\n setHovered(path: LayerPath | null): void {\n this.setUiPath('hovered', path)\n }\n\n /** 路径类 ui 切片的共同形状:值等短路 → 整体替换 → 分支通知 */\n private setUiPath(branch: 'selection' | 'hovered' | 'renaming', path: LayerPath | null): void {\n if (pathsEqual(this.uiValue[branch], path)) return\n this.uiValue = { ...this.uiValue, [branch]: path }\n this.notify({ scope: 'ui', branch })\n }\n\n /** 拖动会话开始/结束(null);会话对象住 ui 分支,文档只收位置事务 */\n setDrag(gesture: DragGesture | null): void {\n this.uiValue = { ...this.uiValue, drag: gesture }\n this.notify({ scope: 'ui', branch: 'drag' })\n }\n\n /** 缩放会话开始/结束(null,工单 07);会话对象住 ui 分支,文档只收缩放事务 */\n setResize(gesture: ResizeGesture | null): void {\n this.uiValue = { ...this.uiValue, resize: gesture }\n this.notify({ scope: 'ui', branch: 'resize' })\n }\n\n /** 建层武装置/解除(null,drag-create 工单 01);标量值等短路(重复武装同型不通知) */\n setArmedCreate(type: LayerType | null): void {\n if (this.uiValue.armedCreate === type) return\n this.uiValue = { ...this.uiValue, armedCreate: type }\n this.notify({ scope: 'ui', branch: 'armedCreate' })\n }\n\n /** 画拉橡皮筋会话开始/更新/结束(null);会话对象住 ui 分支,落库在 endCreate 单事务 */\n setCreate(gesture: CreateGesture | null): void {\n this.uiValue = { ...this.uiValue, create: gesture }\n this.notify({ scope: 'ui', branch: 'create' })\n }\n\n /** 文本编辑会话开始/结束(null);会话住 ui 分支,文本经 commitTextEdit 一次性落文档 */\n setEditing(session: TextEditingSession | null): void {\n this.uiValue = { ...this.uiValue, editing: session }\n this.notify({ scope: 'ui', branch: 'editing' })\n }\n\n /** 重命名会话开始/结束(null);路径类切片,值等短路(重复 begin 同一路径不重绘) */\n setRenaming(path: LayerPath | null): void {\n this.setUiPath('renaming', path)\n }\n\n /** 锚点折叠区开合:布尔值等短路(重复点击同一态不重绘) */\n setAnchorExpanded(open: boolean): void {\n if (this.uiValue.anchorExpanded === open) return\n this.uiValue = { ...this.uiValue, anchorExpanded: open }\n this.notify({ scope: 'ui', branch: 'anchorExpanded' })\n }\n\n /**\n * 新层样式槽逐字段合并(text-defaults 工单 01:捕获/预设/prefs 恢复的统一\n * 写入口):patch 并入现值,五字段值等短路(重复写同值零通知),不进历史。\n */\n setNextTextStyle(patch: Partial<NextTextStyle>): void {\n const next = { ...this.uiValue.nextTextStyle, ...patch }\n if (nextTextStyleEqual(this.uiValue.nextTextStyle, next)) return\n this.uiValue = { ...this.uiValue, nextTextStyle: next }\n this.notify({ scope: 'ui', branch: 'nextTextStyle' })\n }\n\n /**\n * 数据源 schema 声明替换(归一产物或降级 null,校验收口在注入缝不在这里):\n * 同引用短路(重复注入同一归一产物不惊动订阅方);仅 ui 通知,不参与重绘\n * 脏标(onStoreChange 对该分支短路)。\n */\n setDataSourceSchema(schema: ExpressionSchemaNode | null): void {\n if (this.uiValue.dataSourceSchema === schema) return\n this.uiValue = { ...this.uiValue, dataSourceSchema: schema }\n this.notify({ scope: 'ui', branch: 'dataSourceSchema' })\n }\n\n /** 参考线列表替换(增删动作在会话门面收口);仅 ui 通知,不参与历史 */\n setGuides(guides: readonly Guide[]): void {\n this.uiValue = { ...this.uiValue, guides }\n this.notify({ scope: 'ui', branch: 'guides' })\n }\n\n /** 标尺显隐:布尔值等短路(重复置同态不重绘) */\n setRulersVisible(visible: boolean): void {\n if (this.uiValue.rulersVisible === visible) return\n this.uiValue = { ...this.uiValue, rulersVisible: visible }\n this.notify({ scope: 'ui', branch: 'rulersVisible' })\n }\n\n /** 当次命中吸附轴替换(dragTo 副产物 / endDrag 清空);内容等短路(未命中步不重绘) */\n setSnapAxes(axes: readonly SnapAxis[]): void {\n if (snapAxesEqual(this.uiValue.snapAxes, axes)) return\n this.uiValue = { ...this.uiValue, snapAxes: axes }\n this.notify({ scope: 'ui', branch: 'snapAxes' })\n }\n\n /** 锁定集合替换(toggleLayerLock 收口,翻转逻辑在会话门面);内容等短路(集合未变不重绘) */\n setLockedPaths(paths: readonly LayerPath[]): void {\n if (lockedPathsEqual(this.uiValue.lockedPaths, paths)) return\n this.uiValue = { ...this.uiValue, lockedPaths: paths }\n this.notify({ scope: 'ui', branch: 'lockedPaths' })\n }\n\n /**\n * 查找会话替换(beginFind/closeFind/输入写入经会话门面收口到这里);内容等\n * 短路(重复开合/同词写入不通知)。仅 ui 通知,不参与历史。\n */\n setFind(find: FindSession): void {\n const current = this.uiValue.find\n if (\n current.open === find.open\n && current.query === find.query\n && current.replacement === find.replacement\n && current.cursor === find.cursor\n ) {\n return\n }\n this.uiValue = { ...this.uiValue, find }\n this.notify({ scope: 'ui', branch: 'find' })\n }\n\n /**\n * 资源物化状态替换(placeholder-padding-hint 工单 02):宿主从渲染端物化状态机\n * 桥接注入的唯一写入口,整体替换语义;内容等短路(同态快照不惊动订阅方,\n * 桥接的高频整体替换零噪声)。仅 ui 通知,不参与历史、不写 graph(红线 3 延伸)。\n */\n setResourceStatuses(statuses: ResourceStatusMap): void {\n if (resourceStatusesEqual(this.uiValue.resourceStatuses, statuses)) return\n this.uiValue = { ...this.uiValue, resourceStatuses: statuses }\n this.notify({ scope: 'ui', branch: 'resourceStatuses' })\n }\n\n subscribe(listener: Listener): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n private notify(change: EditorChange): void {\n for (const listener of this.listeners) listener(change)\n }\n}\n","/**\n * 快捷键注册表(工单 14):键位→action 的**集中声明**。\n *\n * 注册表即数据(声明式 combo → action 条目),分类器只做匹配与让路;撤销/重做\n * 快捷键自工单 08 的 historyShortcut 并入此表(单表声明,消除双分类器)。缺省集\n * 跟随 excalidraw 惯例并按 v1 单选裁剪:工具切换/全选不可用(单选无对象)。重命\n * 名 F2 自 layer-panel-ux 工单 09 入表(分派到选中根层的重命名编辑会话)。z 序与\n * 缩放键位自 kbd-nav 工单 01 入表(⌘]/⌘[ 前移/后移、⌥⌘]/⌥⌘[ 置顶/置底——Canva/\n * Sketch 同构,弃 Excalidraw ⌘⇧ 系 Safari 换 tab 键位拦截不可靠;⌘0 复位 100%、\n * ⇧1/⇧2 适应画布/选区)。方向键微调自 kbd-nav 工单 02 入表:裸方向键 1px 基础\n * 步、⇧+方向键 10px 大步,方向 × 步长独立 action id(shift 精确匹配先例所致)。\n * 循环选层自 kbd-nav 工单 03 入表:Tab/⇧Tab 沿面板序在根层间移动选中(CONTEXT\n * 「循环选层」词条)——文本编辑与属性面板输入中的 Tab 由让路规则天然放行原生\n * 焦点移动,不劫持。查找替换自 canvas-web-find-replace 工单 01 入表:⌘F 分派\n * beginFind(⌘F 开会话归内核、Esc 关归 Vue 面板组件——内核只持会话态)。样式\n * 粘贴自 canvas-web-style-paste 工单 01 入表:⌥⌘C/⌥⌘V 复制/粘贴样式(CONTEXT\n * 「样式粘贴」词条)。\n * 条目携带 label(中文短句)/ group(展示归组)元数据:\n * 注册表是键位的唯一事实源,帮助面板(kbd-nav 工单 04)直读渲染,后续动作自动\n * 入面板。\n *\n * 让路规则(绑定层折算输入,分类器统一裁决——全部有测试锁定):\n * - 输入法合成中(isComposing || keyCode 229 折算为 composing):候选窗里的按键\n * 属输入法内部编辑;\n * - 文本编辑态(ui.editing ≠ null 折算为 editing):焦点路由进 textarea——Delete\n * 不得删图层、Ctrl/Cmd+Z 撤「输入」而非文档(原生 undo);\n * - 焦点在可编辑元素(input/textarea/select/contentEditable 折算为\n * editableTarget):属性面板/工具栏输入框的原生编辑优先(Delete 删的是输入框\n * 里的字符,不是图层)——重命名输入框内按键同样由此让路。\n *\n * 键匹配双通道:缺省按 KeyboardEvent.key(小写归一)匹配;声明 code 的条目改按\n * KeyboardEvent.code(物理键)匹配——mac ⌥ 修饰下 key 是变体字符(US 布局 ⌥]\n * 非 ']')、Shift+数字是标点变体(US 布局 ⇧1 为 '!'),key 不可靠。mod/shift/alt\n * 全部精确匹配(undo/redo 只差 shift 的先例扩展到 alt:⌘] 条目不吞 ⌥⌘] 按键,\n * 让路给 code 条目)。\n *\n * 本模块无 DOM:KeyboardEvent → 输入的折算归绑定层(useShortcuts)。\n */\nimport type { LayerType } from '@hankchen/canvas'\n\n/** 快捷键动作(会话分派面 executeShortcut 的入参域) */\nexport type EditorShortcutAction =\n | 'undo'\n | 'redo'\n | 'copy'\n | 'paste'\n | 'duplicate'\n | 'copyStyle'\n | 'pasteStyle'\n | 'delete'\n | 'rename'\n | 'findReplace'\n | 'toggleRulers'\n | 'toggleLayerLock'\n | 'toggleLayerVisibility'\n | 'bringForward'\n | 'sendBackward'\n | 'bringToFront'\n | 'sendToBack'\n | 'nudgeUp'\n | 'nudgeDown'\n | 'nudgeLeft'\n | 'nudgeRight'\n | 'nudgeUpCoarse'\n | 'nudgeDownCoarse'\n | 'nudgeLeftCoarse'\n | 'nudgeRightCoarse'\n | 'selectNextLayer'\n | 'selectPrevLayer'\n | 'armCreateText'\n | 'armCreateTable'\n | 'armCreateQrCode'\n | 'armCreateImage'\n | 'zoomReset'\n | 'fitToSurface'\n | 'fitToSelection'\n | 'helpShortcuts'\n\n/** 画拉建层武装四动作的联合(注册表条目与分派、绑定层提示路由的共用键域) */\nexport type ArmCreateShortcutAction = keyof typeof ARM_CREATE_LAYER_TYPES\n\n/**\n * 武装动作 → 层型(canvas-web-drag-create 工单 03):「哪四个动作是武装、各武装\n * 什么层型」的单一事实源——executeShortcut 分派与绑定层桥(useShortcuts 的提示\n * 路由)都从这里取,加层型只改此表与注册表条目两处。\n */\nexport const ARM_CREATE_LAYER_TYPES = {\n armCreateText: 'TextLayer',\n armCreateTable: 'TableLayer',\n armCreateQrCode: 'QrCodeLayer',\n armCreateImage: 'ImageLayer',\n} as const satisfies Record<string, LayerType>\n\n/**\n * 帮助面板的展示归组(kbd-nav 工单 04):分节渲染的键位归类。展示名归 Vue,\n * 这里只定枚举域。text 归组自 canvas-web-find-replace 工单 01 入表(查找替换\n * 等文本编辑面动作,与图层/视图操作分节)。\n */\nexport type EditorShortcutGroup = 'history' | 'clipboard' | 'layer' | 'text' | 'view' | 'help'\n\n/**\n * 键位组合声明:key 为 KeyboardEvent.key 的小写归一形态;mod/shift/alt 精确匹配。\n * 声明 code 时改按 KeyboardEvent.code(物理键)匹配(⌥/⇧ 变体字符场景),key 降级\n * 为条目自述;未声明 code 的条目按 key 匹配、code 不参与。\n */\nexport interface ShortcutCombo {\n key: string\n /** Ctrl(Windows/Linux)或 Cmd(macOS),两平台等价 */\n mod: boolean\n shift: boolean\n /** ⌥(两平台等价);缺省 false 与输入精确匹配(带 ⌥ 的按键不失配到无 ⌥ 条目) */\n alt?: boolean\n /** KeyboardEvent.code:声明即改物理键匹配(mac ⌥ 变体字符 / ⇧ 数字变体) */\n code?: string\n}\n\n/** 注册表条目:键位组合 → 动作(label/group 是帮助面板的展示元数据) */\nexport interface EditorShortcutBinding {\n readonly combo: ShortcutCombo\n readonly action: EditorShortcutAction\n /** 中文短句(帮助面板直读,如「前移一层」) */\n readonly label: string\n /** 展示归组(帮助面板分节) */\n readonly group: EditorShortcutGroup\n}\n\n/**\n * 缺省注册表。undo 与 redo 只差 shift,故 shift 精确匹配(不能忽略);⌥⌘]/⌥⌘[ 与\n * ⇧1/⇧2 按 code 匹配(变体字符),其余条目按 key 匹配。\n */\nexport const DEFAULT_EDITOR_SHORTCUTS: readonly EditorShortcutBinding[] = [\n { combo: { key: 'z', mod: true, shift: false }, action: 'undo', label: '撤销', group: 'history' },\n { combo: { key: 'z', mod: true, shift: true }, action: 'redo', label: '重做', group: 'history' },\n { combo: { key: 'y', mod: true, shift: false }, action: 'redo', label: '重做', group: 'history' },\n { combo: { key: 'c', mod: true, shift: false }, action: 'copy', label: '复制', group: 'clipboard' },\n { combo: { key: 'v', mod: true, shift: false }, action: 'paste', label: '粘贴', group: 'clipboard' },\n { combo: { key: 'd', mod: true, shift: false }, action: 'duplicate', label: '创建副本', group: 'clipboard' },\n // 样式粘贴(canvas-web-style-paste 工单 01):⌥⌘C/⌥⌘V 复制/粘贴样式(Figma/\n // Sketch 同构,行业同键位)。⌥ 修饰下 mac event.key 是变体字符(US 布局\n // ⌥C = 'ç'),key 匹配不可靠——按物理键 code 匹配(kbd-nav 括号条目同门);\n // alt 精确匹配使 ⌘C(无 ⌥)不失配到样式条目、⌥⌘C 也不吞 ⌘C(undo/redo\n // 只差 shift 的先例扩展到 alt)。浏览器 DevTools 占 ⌥⌘C 时右键菜单保底\n {\n combo: { key: 'c', mod: true, shift: false, alt: true, code: 'KeyC' },\n action: 'copyStyle',\n label: '复制样式',\n group: 'clipboard',\n },\n {\n combo: { key: 'v', mod: true, shift: false, alt: true, code: 'KeyV' },\n action: 'pasteStyle',\n label: '粘贴样式',\n group: 'clipboard',\n },\n { combo: { key: 'delete', mod: false, shift: false }, action: 'delete', label: '删除图层', group: 'layer' },\n { combo: { key: 'backspace', mod: false, shift: false }, action: 'delete', label: '删除图层', group: 'layer' },\n { combo: { key: 'f2', mod: false, shift: false }, action: 'rename', label: '重命名图层', group: 'layer' },\n // 查找替换(canvas-web-find-replace 工单 01,CONTEXT「查找替换」词条):⌘F\n // 呼出画布顶部浮动查找条(Canva 同构;浏览器页内查找可 preventDefault 拦截,\n // ⌘D 抢书签先例)。mod+shift 精确匹配先例——⇧⌘F 变体不入表\n {\n combo: { key: 'f', mod: true, shift: false },\n action: 'findReplace',\n label: '查找替换',\n group: 'text',\n },\n // 标尺显隐(ruler-guides-snap 工单 01):裸键 + shift(mod 变体不占用,\n // 不抢浏览器刷新等既有语义)\n { combo: { key: 'r', mod: false, shift: true }, action: 'toggleRulers', label: '标尺显隐', group: 'view' },\n // 锁定 ⇧⌘L / 显隐 ⇧⌘H(canvas-web-layer-lock 工单 02,显隐键位系 feature-status\n // §一挂账补位):行业趋同(Figma/Sketch 同款);mod+shift 组合不抢浏览器\n // ⌘L 地址栏、⌘H 历史页既有语义\n { combo: { key: 'l', mod: true, shift: true }, action: 'toggleLayerLock', label: '锁定/解锁图层', group: 'layer' },\n {\n combo: { key: 'h', mod: true, shift: true },\n action: 'toggleLayerVisibility',\n label: '显示/隐藏图层',\n group: 'layer',\n },\n // z 序四件套(kbd-nav 工单 01):⌘]/⌘[ 前移/后移一格,⌥⌘]/⌥⌘[ 置顶/置底\n // (Canva/Sketch 同构);⌥ 变体字符下 key 不可靠,置顶/置底按物理键 code 匹配\n { combo: { key: ']', mod: true, shift: false }, action: 'bringForward', label: '前移一层', group: 'layer' },\n { combo: { key: '[', mod: true, shift: false }, action: 'sendBackward', label: '后移一层', group: 'layer' },\n {\n combo: { key: ']', mod: true, shift: false, alt: true, code: 'BracketRight' },\n action: 'bringToFront',\n label: '置顶',\n group: 'layer',\n },\n {\n combo: { key: '[', mod: true, shift: false, alt: true, code: 'BracketLeft' },\n action: 'sendToBack',\n label: '置底',\n group: 'layer',\n },\n // 微调(kbd-nav 工单 02):裸方向键 1px 基础步、⇧+方向键 10px 大步,方向 ×\n // 步长独立 action id(shift 精确匹配先例所致,undo/redo 同款);mod/alt 修饰\n // 不入表(⌘←/→ 浏览器历史导航、⌥+方向既有语义不抢)\n { combo: { key: 'arrowup', mod: false, shift: false }, action: 'nudgeUp', label: '微调上移', group: 'layer' },\n { combo: { key: 'arrowdown', mod: false, shift: false }, action: 'nudgeDown', label: '微调下移', group: 'layer' },\n { combo: { key: 'arrowleft', mod: false, shift: false }, action: 'nudgeLeft', label: '微调左移', group: 'layer' },\n { combo: { key: 'arrowright', mod: false, shift: false }, action: 'nudgeRight', label: '微调右移', group: 'layer' },\n { combo: { key: 'arrowup', mod: false, shift: true }, action: 'nudgeUpCoarse', label: '大步上移', group: 'layer' },\n { combo: { key: 'arrowdown', mod: false, shift: true }, action: 'nudgeDownCoarse', label: '大步下移', group: 'layer' },\n {\n combo: { key: 'arrowleft', mod: false, shift: true },\n action: 'nudgeLeftCoarse',\n label: '大步左移',\n group: 'layer',\n },\n {\n combo: { key: 'arrowright', mod: false, shift: true },\n action: 'nudgeRightCoarse',\n label: '大步右移',\n group: 'layer',\n },\n // 循环选层(kbd-nav 工单 03):Tab 朝面板垫底方向、⇧Tab 反向(shift 精确\n // 匹配先例——⇧Tab 不落裸 Tab 条目);mod/alt 修饰不入表(⌘Tab/⌥Tab 浏览器\n // 与系统既有语义不抢)。文本编辑/输入框中的 Tab 由让路规则天然放行原生焦点\n // 移动,不劫持\n { combo: { key: 'tab', mod: false, shift: false }, action: 'selectNextLayer', label: '循环选下一层', group: 'layer' },\n { combo: { key: 'tab', mod: false, shift: true }, action: 'selectPrevLayer', label: '循环选上一层', group: 'layer' },\n // 画拉建层武装四条目(canvas-web-drag-create 工单 03,CONTEXT「武装」词条):\n // T/G/Q/I 层型待命——下一次画布按下即开画拉,建层或 Esc 即解除(一次性待命,\n // 无粘性工具)。按物理键 code 匹配(⇧1/⇧2 数字行同门先例):非拉丁布局的\n // key 是本地字符(西里尔布局 T 位产出 'т'),code 恒 KeyT 两边通吃;mod/shift/\n // alt 精确匹配(⌘T 新标签、⇧T、⌥T 变体不入表)。文本编辑态/输入框焦点由让路\n // 规则天然放行(编辑文本时 T 打字不武装)——绑定层 useShortcuts 桥对命中条目\n // 直写状态栏瞬时提示,不经本表\n { combo: { key: 't', mod: false, shift: false, code: 'KeyT' }, action: 'armCreateText', label: '画拉建文本层', group: 'layer' },\n { combo: { key: 'g', mod: false, shift: false, code: 'KeyG' }, action: 'armCreateTable', label: '画拉建表格', group: 'layer' },\n { combo: { key: 'q', mod: false, shift: false, code: 'KeyQ' }, action: 'armCreateQrCode', label: '画拉建二维码', group: 'layer' },\n { combo: { key: 'i', mod: false, shift: false, code: 'KeyI' }, action: 'armCreateImage', label: '画拉建图片', group: 'layer' },\n // 缩放(kbd-nav 工单 01):⌘0 复位 100%(视口中心为锚;浏览器吞键时的降级\n // 预案 = Figma 纯 shift ⇧0/1/2,宿主注入亦可覆盖);⇧1/⇧2 适应画布/选区,\n // 数字行按 code 匹配(US 布局 ⇧1 的 key 是 '!',法国布局是 '1',code 恒\n // Digit1 两边通吃)\n { combo: { key: '0', mod: true, shift: false }, action: 'zoomReset', label: '缩放复位 100%', group: 'view' },\n { combo: { key: '1', mod: false, shift: true, code: 'Digit1' }, action: 'fitToSurface', label: '适应画布', group: 'view' },\n {\n combo: { key: '2', mod: false, shift: true, code: 'Digit2' },\n action: 'fitToSelection',\n label: '适应选区',\n group: 'view',\n },\n // 帮助面板(kbd-nav 工单 04):⌘/ 开合快捷键帮助。UI 面动作——对话态是 UI\n // 关注点,绑定层桥(useShortcuts)拦截路由到 useShortcutsHelp 单例态,不经\n // 内核 dispatcher、不进内核 store;注册表是键位唯一事实源,条目在列帮助面板\n // 才渲染(group 'help' 系元数据扩展时预留的归组)\n {\n combo: { key: '/', mod: true, shift: false },\n action: 'helpShortcuts',\n label: '快捷键帮助',\n group: 'help',\n },\n]\n\n/**\n * 快捷键输入:绑定层从 KeyboardEvent 与会话状态折算(本类型不出现任何 DOM 类型)。\n * alt/code 为可选——未折算的宿主按缺省(无 ⌥、无 code)参与匹配。\n */\nexport interface EditorShortcutInput {\n /** KeyboardEvent.key(大小写随 Shift 与大小写锁定,分类器归一) */\n key: string\n mod: boolean\n shift: boolean\n /** KeyboardEvent.altKey 折算;缺省(未折算)视作 false */\n alt?: boolean\n /** KeyboardEvent.code 折算;声明 code 的注册表条目只认它,缺省(未折算)恒不命中 */\n code?: string\n /** 输入法合成中:isComposing || keyCode === 229 由绑定层折算 */\n composing: boolean\n /** 文本编辑态(ui.editing ≠ null):全部文档快捷键让路给 textarea */\n editing: boolean\n /** 焦点在可编辑元素(输入框/可编辑节点):让路给原生编辑 */\n editableTarget: boolean\n}\n\n/**\n * 键位 → 动作分类:让路规则优先(合成中/编辑态/输入态一律 null),再按注册表\n * 匹配——声明 code 的条目按 input.code 全等(mod/shift/alt 精确),其余按 key\n * 小写归一后全等。未命中返回 null。绑定集可注入(宿主扩展键位;缺省\n * DEFAULT_EDITOR_SHORTCUTS)。\n */\nexport function classifyEditorShortcut(\n input: EditorShortcutInput,\n bindings: readonly EditorShortcutBinding[] = DEFAULT_EDITOR_SHORTCUTS,\n): EditorShortcutAction | null {\n if (input.composing || input.editing || input.editableTarget) return null\n const key = input.key.toLowerCase()\n const alt = input.alt ?? false\n for (const binding of bindings) {\n const keyMatches = binding.combo.code !== undefined ? binding.combo.code === input.code : binding.combo.key === key\n if (\n keyMatches &&\n binding.combo.mod === input.mod &&\n binding.combo.shift === input.shift &&\n (binding.combo.alt ?? false) === alt\n ) {\n return binding.action\n }\n }\n return null\n}\n","/**\n * 编辑器状态切片与偏好(project-data 工单 01,spec §3/§5):内核导出的「单画布\n * 切片 + 偏好 + schemaVersion 常量」三件,EditorSession 实例方法(editor.ts)消费。\n *\n * 分层纪律(内核帧盲):帧名/帧缓冲/文档是宿主领域——frames 容器(帧名 → 切片)\n * 与完整快照聚合类型(schemaVersion/frames/prefs)归宿主拼装,宿主只透传切片\n * 内容、不解释 guides/lockedPaths 语义;本模块只出两型 + 版本常量 + restore 的\n * 轻结构校验。零契约面:切片不进 wire、不进历史栈、不是第二种文档格式——graph\n * 仍是内核唯一保存物。\n */\nimport type { Guide, GuideOrientation } from '../spatial/snap'\nimport type { LayerPath } from '../shared/layerPath'\nimport type { NextTextStyle } from '../editing/layerPanel'\n\n/** 快照格式版本:随格式演化单调递增;v1 = 1(宽容读贯彻,restore 不分支此值) */\nexport const EDITOR_STATE_SCHEMA_VERSION = 1\n\n/** 帧作用域切片:当前打开画布的编辑器状态(工程数据快照的 frames[帧名] 值) */\nexport interface CanvasEditorState {\n /**\n * 参考线(场景坐标):id 是会话内自增值、无持久语义——export 原样带出,\n * restore 端清空后逐条 addGuide 重取号,快照 id 出方向即失效。\n */\n guides: readonly Guide[]\n /** 锁定根层路径集合(随工程数据持久化,仍零 wire 面) */\n lockedPaths: readonly LayerPath[]\n}\n\n/** 编辑器级偏好:跨文档、跨会话内核自持(工程数据快照的 prefs 桶,分类学终裁三项) */\nexport interface EditorPrefsState {\n rulersVisible: boolean\n anchorExpanded: boolean\n /**\n * 新层样式槽(text-defaults 工单 01,CONTEXT「新层样式」词条):随 prefs 桶\n * 跨会话持久(per-模板记忆)。schemaVersion 不升——加法演进,旧快照缺字段\n * 由 applyEditorPrefs 宽容读兜底为现值(会话内即工厂缺省)。\n */\n nextTextStyle: NextTextStyle\n}\n\n/** 快照 guide 的恢复形:id 已剥(无持久语义),只余 addGuide 入参两字段 */\nexport interface GuidePlacement {\n orientation: GuideOrientation\n position: number\n}\n\n/** 校验产出的恢复切片:guide 已剥壳为 addGuide 入参形(快照 id 丢弃),路径外壳原样 */\nexport interface ParsedEditorStateSlice {\n guides: readonly GuidePlacement[]\n lockedPaths: readonly LayerPath[]\n}\n\n/**\n * 轻结构校验(restoreEditorState 的唯一入口):形状不合法整体拒(返 null,调用\n * 方返 false 宿主记因),合法但内容悬空不算拒——锁定路径只验「是数组」外壳,\n * 可解析性(越界/形态与文档不符)归 pruneDanglingPaths 按文档裁定。宽容读:\n * 切片与 guide 条目的未知键忽略、guide id 不校验不透传(重排重取号,快照 id 无\n * 持久语义);guides/lockedPaths 两载荷键缺一或非数组即形状不完整,整体拒。\n * 载荷来自宿主层 JSON 快照(存储端零校验),类型层的形状信任不了,运行时验。\n */\nexport function parseCanvasEditorState(state: CanvasEditorState): ParsedEditorStateSlice | null {\n const raw = state as unknown\n if (raw === null || typeof raw !== 'object') return null\n const source = raw as Record<string, unknown>\n if (!Array.isArray(source.guides) || !Array.isArray(source.lockedPaths)) return null\n const guides: GuidePlacement[] = []\n for (const entry of source.guides as unknown[]) {\n if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) return null\n const { orientation, position } = entry as Record<string, unknown>\n if (orientation !== 'horizontal' && orientation !== 'vertical') return null\n if (typeof position !== 'number' || !Number.isFinite(position)) return null\n guides.push({ orientation, position })\n }\n for (const path of source.lockedPaths as unknown[]) {\n if (!Array.isArray(path)) return null\n }\n return { guides, lockedPaths: source.lockedPaths as readonly LayerPath[] }\n}\n","/**\n * 字体清单(工单 13):编辑会话级的字体可选清单——「清单 ≠ 物化」,core 只持有\n * 引用与展示名,字体的加载仍走渲染端物化管线(FontFace 注册),本模块零 I/O。\n *\n * - 内置清单:宿主在创建会话时配置(URL 列表可配置;缺省为空——宿主不配置即\n * 只有内置默认字体与自定义上传项)。\n * - 自定义字体:上传动作(uploadFont)成功后追加,会话内即可选可用;清单是\n * 会话状态不是文档数据,不进 graph(往返恒等契约禁止私加字段),保存/打开\n * 不携带。\n */\nexport interface FontCatalogEntry {\n /** 展示名(清单选择器显示;与引用解耦,URL 不直接暴露给编辑者) */\n readonly label: string\n /** 字体引用(URL / data URL),与 TextLayer.font 同域;物化管线按引用装载 */\n readonly ref: string\n}\n\ntype Listener = (entries: readonly FontCatalogEntry[]) => void\n\nexport class FontCatalog {\n private readonly builtin: readonly FontCatalogEntry[]\n private custom: readonly FontCatalogEntry[] = []\n private readonly listeners = new Set<Listener>()\n\n constructor(builtin: readonly FontCatalogEntry[] = []) {\n this.builtin = [...builtin]\n }\n\n /** 当前清单:内置在前(配置序),自定义追加在后(上传序) */\n get entries(): readonly FontCatalogEntry[] {\n return [...this.builtin, ...this.custom]\n }\n\n /** 追加自定义条目;同引用去重(重复上传不产生重复项,首见条目胜出) */\n addCustom(entry: FontCatalogEntry): boolean {\n if (this.entries.some((existing) => existing.ref === entry.ref)) return false\n this.custom = [...this.custom, entry]\n this.notify()\n return true\n }\n\n /** 订阅清单变更(整体快照推送,绑定层的响应式 ref 据此联动);返回退订函数 */\n subscribe(listener: Listener): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n\n private notify(): void {\n const entries = this.entries\n for (const listener of this.listeners) listener(entries)\n }\n}\n","/**\n * 上传注入点(工单 13):本机资源 → 可物化引用的宿主实现契约。\n *\n * core 只定义接口与消费动作,**不内置任何上传实现**(红线:图层 setter 禁 I/O\n * 的延伸——文档只持有引用字符串,本机字节到引用的搬运归宿主):生产宿主接自己\n * 的对象存储/上传服务返回 URL;playground 以 data URL 兜底(文件不经网络,可离\n * 线演示,代价是引用随 graph JSON 一起变大——演示语义,不作生产形态)。\n *\n * 文件以 DOM 无关的最小形态描述(core 无 DOM lib):绑定层从宿主 File 对象读出\n * 名称/MIME/字节后传入,内核不接触 File/Blob 等宿主类型。\n */\n\n/** 本机资源文件的最小描述(DOM 无关) */\nexport interface UploadFile {\n /** 原始文件名(含扩展名;上传实现可据其推断存储键/展示名) */\n readonly name: string\n /** MIME 类型(如 image/png、font/ttf;未知时可为空串) */\n readonly mime: string\n /** 文件字节 */\n readonly bytes: Uint8Array\n}\n\n/** 本机资源 → 可物化引用(URL / data URL / 宿主存储地址,渲染端物化管线可装载的引用) */\nexport type UploadHandler = (file: UploadFile) => Promise<string>\n\n/**\n * 上传建层的摆位覆盖(kbd-nav 工单 05,拖文件入画布语义):缺省(两键皆缺)=\n * 编辑器缺省盒(createDefaultLayer 的 ImageLayer 形态 200×150 落 0,0)。\n * - `at`:图层盒左上角的场景坐标(缺省 top-left 锚不改,改写锚点偏移);\n * - `size`:盒尺寸 1:1 落值不缩放(拖放语义传解码出的图片自然尺寸,溢出画布\n * 属预期——Figma 同构)。\n */\nexport interface UploadImagePlacement {\n readonly at?: { x: number; y: number }\n readonly size?: { width: number; height: number }\n}\n\n/** 上传条目的展示名:去扩展名的文件名基名(空退化全名) */\nexport function uploadDisplayName(file: UploadFile): string {\n const base = file.name.replace(/\\.[^.]+$/, '')\n return base === '' ? file.name : base\n}\n\n/** 未注入上传实现时的统一错误(宿主侧降级提示的对位语义) */\nexport class UploadHandlerMissingError extends Error {\n constructor() {\n super('未注入上传实现(uploadHandler):宿主可在创建 EditorSession 时提供本机资源 → 可物化引用的实现')\n this.name = 'UploadHandlerMissingError'\n }\n}\n","/**\n * spec 字段路径读写原语(shared):沿图层内字段路径(如 ['shape','backgroundColor']、\n * 单段 ['fontSize'])下降读取/原位写入。updateSpec(属性面板权威写入口)与样式\n * 粘贴(editing/styleClipboard 的快照抓取与逐字段落地)共用同一套下降语义。\n *\n * 中间段悬空(null/非对象)即放弃:字段形态由上层(领域类型/注册表)把关,\n * 原语只做防御性下降。写入侧尾段原位赋值,immer draft 语义下生效。\n */\n\n/** 沿字段路径下降读取;目标不是对象或中间段悬空返回 undefined */\nexport function readSpecField(target: unknown, key: readonly string[]): unknown {\n let node: unknown = target\n for (let i = 0; i < key.length - 1; i += 1) {\n if (node === null || typeof node !== 'object') return undefined\n node = (node as Record<string, unknown>)[key[i]!]\n }\n if (node === null || typeof node !== 'object') return undefined\n return (node as Record<string, unknown>)[key[key.length - 1]!]\n}\n\n/**\n * 沿字段路径下降写入(中间段悬空即放弃;尾段原位赋值,immer draft 语义下生效)。\n * 返回是否真写入——调用方据此统计落地字段(悬空放弃不计)。\n */\nexport function writeSpecField(target: unknown, key: readonly string[], value: unknown): boolean {\n if (target === null || typeof target !== 'object') return false\n let node: unknown = target\n for (let i = 0; i < key.length - 1; i += 1) {\n const next: unknown = (node as Record<string, unknown>)[key[i]!]\n if (next === null || typeof next !== 'object') return false\n node = next\n }\n ;(node as Record<string, unknown>)[key[key.length - 1]!] = value\n return true\n}\n","/**\n * 样式剪贴板内核语义(canvas-web-style-paste 工单 01):样式集/适用面常量 +\n * 复制快照/粘贴落地的纯函数面。\n *\n * - 样式 = 三族字段子集(spec 决策 2):盒内对齐(align.horizontal/vertical)、\n * 形状皮肤(shape.backgroundColor/shape.border/shape.padding)、文本族\n * (font/fontSize/fontColor/autowrap/angle/shape.lineHeight,仅文本层适用;\n * lineHeight 虽住 shape 段但随文本族走跨型规则)。明确排除:位置/尺寸/锚点\n * (几何不是样式)、name/visible/priority、数据字段 text/src/value(内容走\n * 复制/粘贴本体)、expression、表格结构字段(template/rows/rowsPath)。\n * - 适用面清单(spec 决策 3)按 LayerType 由内核 editing 域自持——对齐\n * editor-vue fieldSchema 的分组事实但**不引它**(依赖方向红线):fieldSchema\n * 给行模板替身只暴露高/高自适应(尺寸族不在样式集),故其适用面为空。\n * - 快照 = {sourceType, values}:逐字段 JSON 深拷贝(与源层断开引用,后续编辑\n * 源层不影响);拷贝不进历史(剪贴板态不是文档态,session 槽同门)。\n * - 粘贴 = 目标类型适用面 ∩ 快照字段逐字段 verbatim 覆盖:null 即值(无填充/\n * 无边框是样式事实)、Border 整对象覆盖;源不适型字段静默跳过;全等字段\n * 跳写——结构等值的新引用 immer 仍记 patch,跳写保「零变化 = 空 patch =\n * 不进历史」真短路。autoHeight⟹height=0 断言与 canonicalizeTableSync 重断言\n * 在 session 事务内一次收口(updateSpec 同门),本模块只做字段读写与判定。\n */\n\nimport type { Draft } from 'immer'\n\nimport type { Layer, LayerType } from '@hankchen/canvas'\n\nimport { readSpecField, writeSpecField } from '../shared/specField'\n\n/** 盒内对齐族(对齐 fieldSchema 对齐组) */\nconst ALIGN_FAMILY: readonly (readonly string[])[] = [\n ['align', 'horizontal'],\n ['align', 'vertical'],\n]\n\n/** 形状皮肤族(对齐 fieldSchema 形状组的样式三键;宽高/auto 不属样式) */\nconst SKIN_FAMILY: readonly (readonly string[])[] = [\n ['shape', 'backgroundColor'],\n ['shape', 'border'],\n ['shape', 'padding'],\n]\n\n/** 文本族(fieldSchema 文本组 + TextLayer 专属 angle 与 shape.lineHeight) */\nconst TEXT_FAMILY: readonly (readonly string[])[] = [\n ['font'],\n ['fontSize'],\n ['fontColor'],\n ['autowrap'],\n ['angle'],\n ['shape', 'lineHeight'],\n]\n\n/**\n * 按 LayerType 的样式字段适用面(spec 决策 3):键为图层内字段路径。行模板替身\n * 适用面为空——面板只暴露高/高自适应(尺寸族不在样式集),无样式可复制可贴。\n */\nexport const STYLE_FIELD_KEYS_BY_TYPE: Readonly<Record<LayerType, readonly (readonly string[])[]>> = {\n TextLayer: [...ALIGN_FAMILY, ...SKIN_FAMILY, ...TEXT_FAMILY],\n ImageLayer: [...ALIGN_FAMILY, ...SKIN_FAMILY],\n QrCodeLayer: [...ALIGN_FAMILY, ...SKIN_FAMILY],\n TableLayer: [...ALIGN_FAMILY, ...SKIN_FAMILY],\n TableRowLayer: [...ALIGN_FAMILY, ...SKIN_FAMILY],\n TableCellLayer: [...ALIGN_FAMILY, ...SKIN_FAMILY],\n TableRowTemplate: [],\n}\n\n/** 样式快照(样式剪贴板条目):源类型 + 适用面逐字段值 */\nexport interface StyleSnapshot {\n readonly sourceType: LayerType\n /** 字段值:键 = 字段路径点串(如 'align.horizontal'),值 = JSON 深拷贝 */\n readonly values: Readonly<Record<string, unknown>>\n}\n\n/** 字段键 → 快照记录键(点串;字段段为固定领域名,无歧义) */\nconst fieldId = (key: readonly string[]): string => key.join('.')\n\n/** 领域类型是纯 JSON 形态(往返恒等契约),JSON 往返即深拷贝(cloneLayerSubtree 同门) */\nfunction deepClone<T>(value: T): T {\n return JSON.parse(JSON.stringify(value)) as T\n}\n\n/** 结构等值(stringify 即指纹;undefined === undefined 为真——两侧缺字段视为全等) */\nfunction jsonEqual(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b)\n}\n\n/**\n * 按源类型适用面抓逐字段深拷贝快照;适用面为空(行模板替身)返回 null——\n * 无样式可复制,调用方空转(空快照不占槽,已拷贝的样式不被无样式源清掉)。\n */\nexport function captureStyleSnapshot(layer: Layer): StyleSnapshot | null {\n const fields = STYLE_FIELD_KEYS_BY_TYPE[layer.type]\n if (fields.length === 0) return null\n const values: Record<string, unknown> = {}\n for (const key of fields) {\n const value = readSpecField(layer, key)\n if (value !== undefined) values[fieldId(key)] = deepClone(value)\n }\n return { sourceType: layer.type, values }\n}\n\n/** 逐字段应用快照到图层 draft(目标适用面 ∩ 快照):verbatim 覆盖、null 即值、\n * Border 整对象;快照没有的字段(源不适型)静默跳过;全等字段跳写。返回实际\n * 写入的字段键(空数组 = 零变化,调用方经 store 空 patch 空转)。\n */\nexport function applyStyleFieldsInDraft(\n layer: Draft<Layer>,\n snapshot: StyleSnapshot,\n): (readonly string[])[] {\n const written: (readonly string[])[] = []\n for (const key of STYLE_FIELD_KEYS_BY_TYPE[layer.type]) {\n const value = snapshot.values[fieldId(key)]\n if (value === undefined) continue // 源不适型:快照无此字段\n const current = readSpecField(layer, key)\n if (jsonEqual(current, value)) continue // 全等跳写:保零变化真短路\n if (writeSpecField(layer, key, deepClone(value))) written.push(key)\n }\n return written\n}\n","/**\n * 滚轮意图分类(工单 05,三态)。纯函数:输入只取事件修饰键快照,\n * 由绑定层(CanvasSurface)把 WheelEvent 映射进来后在内核判定。\n *\n * 语义(spec 拍板):ctrl/cmd + wheel 与触控板双指捏合 = 缩放(浏览器把\n * pinch 一律派发为 ctrl+wheel)、plain wheel = 平移、shift + wheel = 横向平移。\n */\n\nexport type WheelIntent = 'zoom' | 'pan' | 'pan-x'\n\n/** WheelEvent 的语义字段快照(内核不持有 DOM 事件类型) */\nexport interface WheelInput {\n ctrlKey: boolean\n metaKey: boolean\n shiftKey: boolean\n}\n\nexport function classifyWheel(input: WheelInput): WheelIntent {\n // 缩放优先于 shift:捏合手势(ctrl+wheel)不因 shift 串台为横移\n if (input.ctrlKey || input.metaKey) return 'zoom'\n if (input.shiftKey) return 'pan-x'\n return 'pan'\n}\n","/**\n * 内建函数已知名单与格式建议表(expression-type-methods 工单 12,spec §1.2 ⑨/§2/§3.4)。\n *\n * spec §7 JS 行:结构面识别、名单面不校验;**已知名单只喂补全建议**——本表是\n * v1 白名单(date/number/fixed/percent)的编辑器侧建议数据,不参与任何校验/标红\n * (PHP 权威端先发新函数、编辑器后跟期间避免「求值正常但编辑器假红」的名单漂移;\n * PHP 增函数时此处增量跟随,缺位代价只是少建议不误红)。\n *\n * 具名预设/中文模板快捷项(spec §3.4):预设是求值面纯糖('date'/'datetime' 整体\n * 等值命中展开);中文格式不设求值预设——`YYYY年MM月DD日` 写即所得,编辑器快捷项\n * 与求值面解耦(spec 原文口径)。\n *\n * 全模块零依赖纯数据、零 DOM(editor-shared-isolation)。\n */\n\n/** 一条内建函数建议元数据(segment 由消费方拼装:`名字()` + 光标回落括号内) */\nexport interface ExpressionFunctionMeta {\n name: string\n /** 签名展示(候选 tooltip/说明位) */\n signature: string\n /** 一句话语义(候选说明位) */\n summary: string\n}\n\n/** v1 内建函数白名单的建议面数据(spec §2;顺序 = 文档序,非优先级) */\nexport const EXPRESSION_FUNCTIONS: readonly ExpressionFunctionMeta[] = [\n { name: 'date', signature: 'date(path[, format])', summary: '日期格式化(moment 风格 token,预设 date/datetime)' },\n { name: 'number', signature: 'number(path[, n])', summary: '千分位分组' },\n { name: 'fixed', signature: 'fixed(path[, n])', summary: '定点补零(缺省 2 位)' },\n { name: 'percent', signature: 'percent(path[, n])', summary: '百分比(乘 100)' },\n]\n\n/** 数字族桶建议(工单 11 ②:type=number 字段建议三函数,插入裸调用走缺省参) */\nexport const NUMBER_FUNCTION_SUGGESTIONS: readonly ExpressionFunctionMeta[] = EXPRESSION_FUNCTIONS.filter((fn) =>\n fn.name !== 'date',\n)\n\n/** 一条格式串快捷项(date 第二参字符串槽的建议面) */\nexport interface ExpressionFormatSuggestion {\n segment: string\n description: string\n}\n\n/** date 格式串快捷项:具名预设两枚 + 中文模板(spec §3.4;写即所得不设中文求值预设) */\nexport const DATE_FORMAT_SUGGESTIONS: readonly ExpressionFormatSuggestion[] = [\n { segment: 'date', description: '预设 → YYYY-MM-DD' },\n { segment: 'datetime', description: '预设 → YYYY-MM-DD HH:mm:ss' },\n { segment: 'YYYY年MM月DD日', description: '中文模板(写即所得)' },\n]\n","/**\n * 候选枚举器(content-completion 工单 02,spec §2 候选枚举规则表)。\n *\n * 规则逐条导出自 php-canvas-next 求值/填充语义(InterpolationEvaluator +\n * CanvasHydrator,只读对齐),非新发明:\n * - 根上下文(根层三字段)头部候选 = 载荷顶层键(D1:schema 声明即 data 载荷\n * 形状)+ `$root`;行上下文(格内容层)追加 `row` + `$index`。`$index` 仅行\n * 上下文给——行外求值为空串非报错,但不给写得出来而无效的表达式;`row.` 行外\n * 报 `expression_row_outside_loop`,同样不给。\n * - 裸键在两种上下文都走根 schema:行上下文求值上下文 = `$rowNamespace + $root`\n * 合并(CanvasHydrator::evaluateNode),行命名空间只有 row/$index 且\n * reserved_root_key 保证根级键不遮蔽——裸键即根键。\n * - `.` 下钻走 properties 树;无 properties 节点终止该分支;数组节点收敛为叶子\n * 无具名子候选(D9 修订 D3:具名段下钻数组在求值器是静默空串——缺字段信号,\n * 不给写得出来而渲染为空白的表达式;下钻层 items 链保留供 rowsPath/`row.*`\n * 机制,类型徽标仍显示 array 本身)。开放映射(open)节点的枚举结果携带 open\n * 信号(结果面扩展,不污染候选条目契约),供工单 10 浮层占位提示(D10)。\n *\n * 消费契约(工单 04 浮层 + 工单 12 调用面扩展):\n * - 入参 expr 是**光标前缀**(trim 后,来自 expressionScan + 光标切片——调用片段\n * 编辑位常在中段,工单 12);**单个尾点 = 补全点标记**,先剥再解析(对齐工单 01\n * 备注:`row.` 按求值语义是空段错误,剥点后 `row` 才是可下钻前缀);剥点后为空\n * 且原 expr 非空 = 孤点前缀,无合法续写 → expression_syntax_error 信号。\n * - 结果 `partial` 恒为 expr 的后缀(尾点态 = 空后缀):接受 = 在光标锚位把\n * partial 替换为候选 segment;候选给**全量合法子项**、不按 partial 预过滤,\n * 前缀过滤/高亮归浮层(Ctrl+Space 全量展示语义亦归浮层)。\n * - 元信息:path(补全后的完整点路径)+ schema description/title(D8 两字段分离\n * 透传,展示回落 description ?? title 归浮层)+ 类型徽标数据(type 归一)。\n * `$root`/`row`/`$index` 结构头是下钻踏脚石(`{{$root}}`/\n * `{{row}}` 单独求值无意义),元信息仅有行 schema 声明时的 row 携带。\n * - 内建函数建议面(工单 12,spec §1.2 ⑨「已知名单只喂补全建议」):\n * ① 函数名候选——片段以名字形态开头(非空裸名)时给 `名字()` 插入项\n * (cursorBack 落括号内);空 expr 与尾点下钻位不给(字段列表零噪音)。\n * ② 建议分桶(工单 11 ②)——partial 非空(字段收敛中)时按子节点形状追加\n * 调用插入候选:format 标记 string → `date(path, '')`(光标落引号内 = format\n * 参数占位)、number → 数字族三函数;普通 string 不特供。桶候选携带\n * replaceLen = 整段 expr(接受时重写整个前缀+部分段)与 filterKey = 键名\n * (与字段候选同前缀过滤)。\n * ③ 调用槽位路由——expr 已是调用形态(⑦)时按光标槽位分派:首参路径槽复用\n * 路径枚举(关桶/函数建议:调用不嵌套);date 字符串槽给具名预设 + 中文模板\n * 快捷项(spec §3.4);其余槽位/闭合调用空候选。结构红标归扫描面,此处不判。\n *\n * 降级语义:schema null(声明被拒/形态非法,经 normalizeExpressionSchemaSource\n * 收口)→ 全部无候选;schema 合法而行 schema 未解析(rowsPath 无 items 声明)→\n * 结构头 row 仍在、行子树无候选——辅助声明不作权威,全程不抛错。\n *\n * 全模块纯函数、无 DOM;层内依赖仅 expressionSchema / expressionPath /\n * expressionCall / expressionFunctions。\n */\nimport { isCallFragment, expressionCallSlotAtEnd } from './expressionCall'\nimport { DATE_FORMAT_SUGGESTIONS, EXPRESSION_FUNCTIONS, NUMBER_FUNCTION_SUGGESTIONS } from './expressionFunctions'\nimport { parseExpressionPath, type ExpressionPathHead, type ExpressionSyntaxError } from './expressionPath'\nimport {\n schemaChildEntries,\n schemaNodeAtPath,\n type ExpressionSchemaChildEntry,\n type ExpressionSchemaNode,\n} from './expressionSchema'\n\n/** 'root' = 根层三字段(独立图层);'row' = 格内容层(模板行内) */\nexport type ExpressionContextKind = 'root' | 'row'\n\n/** 一条补全候选:路径 + schema 元信息(description/title/类型徽标,展示回落归浮层) */\nexport interface ExpressionCandidate {\n /** 补全后的完整点路径(前缀 + 本段;头部候选即头部名;调用候选 = 插入文本) */\n path: string\n /** 本段名——接受时替换 partial / 追加到前缀后的文本 */\n segment: string\n /** schema description(未声明为 undefined) */\n description?: string\n /** schema title(D8 形状树两字段分离透传,未声明为 undefined)——浮层展示取 description ?? title */\n title?: string\n /** 类型徽标数据(schema type 归一;未声明为 undefined;函数/格式候选为建议类别) */\n type?: string\n /**\n * 插入后光标从插入文本末端回退的码元数(工单 12):date() 调用 = 2(落引号内\n * format 参数占位)、`名字()` 函数名候选 = 1(落括号内);缺省 0(落插入末尾)\n */\n cursorBack?: number\n /**\n * 接受时从光标锚位回退替换的长度(工单 12;缺省 = partial 长度):桶候选携带\n * 整段 expr 长度——调用插入重写「前缀 + 部分段」全量\n */\n replaceLen?: number\n /**\n * 前缀过滤键(工单 12;缺省 = segment):桶候选 = 字段键名——与同键路径候选\n * 一起被浮层按 partial 过滤(segment 是整段调用文本,直接过滤永不匹配)\n */\n filterKey?: string\n}\n\nexport interface ExpressionCandidateQuery {\n context: ExpressionContextKind\n /** 当前片段表达式(trim 后;'' = 刚输入 {{;允许单个补全尾点) */\n expr: string\n /**\n * 行上下文的行键树 schema(rowsPath 数组 items,resolveRowSchema 产物)。\n * 嵌套模板表的行相对 rowsPath 由调用方按当前行 schema 解析后传入。\n * 未传/解析不出 = 行子树无候选,结构头 row 仍给。\n */\n rowSchema?: ExpressionSchemaNode | null\n}\n\nexport type ExpressionCandidateResult =\n | {\n ok: true\n /** 候选所属前缀路径('' = 上下文头部层) */\n prefix: string\n /** 正在输入的未完整段('' = 尾点/空表达式态);恒为 expr 的后缀 */\n partial: string\n candidates: readonly ExpressionCandidate[]\n /**\n * 开放映射信号(D10):候选来源节点(头部层 = 根 schema,下钻层 = 前缀\n * 节点)标 open 时为 true,非 open 恒缺省——供浮层渲染「动态字段,键由\n * 模板定义」占位提示(工单 10),不污染候选条目契约。\n */\n open?: boolean\n }\n | { ok: false; reason: ExpressionSyntaxError }\n\nfunction headName(head: ExpressionPathHead): string {\n if (head.kind === 'row') return 'row'\n if (head.kind === 'root') return '$root'\n return head.name\n}\n\nfunction candidateMeta(node: ExpressionSchemaNode): Pick<ExpressionCandidate, 'description' | 'title' | 'type'> {\n const meta: Pick<ExpressionCandidate, 'description' | 'title' | 'type'> = {}\n if (node.description !== undefined) meta.description = node.description\n if (node.title !== undefined) meta.title = node.title\n if (node.type !== undefined) meta.type = node.type\n return meta\n}\n\nfunction childCandidate(entry: ExpressionSchemaChildEntry, prefix: string): ExpressionCandidate {\n const path = prefix === '' ? entry.key : `${prefix}.${entry.key}`\n return { path, segment: entry.key, ...candidateMeta(entry.node) }\n}\n\n/**\n * 建议分桶(工单 11 ②消费,工单 12 落地):按子节点形状在路径候选后追加调用\n * 插入候选——format 标记 string → `date(path, '')`(cursorBack 2 = 光标落引号内\n * format 参数占位,预设/模板快捷项由调用槽位路由接棒);number → 数字族三函数\n * (裸调用走缺省参);其余形状不特供。只影响建议面,求值三端零感知。\n * replaceLen = 整段 expr:接受时重写「前缀 + 部分段」(调用包住已输入的路径);\n * filterKey = 键名:与同键路径候选一起按 partial 前缀过滤。\n */\nfunction callCandidatesFor(entry: ExpressionSchemaChildEntry, path: string, exprLen: number): ExpressionCandidate[] {\n const extras: ExpressionCandidate[] = []\n if (entry.node.format === 'date' || entry.node.format === 'date-time') {\n extras.push({\n path: `date(${path}, '')`,\n segment: `date(${path}, '')`,\n cursorBack: 2,\n replaceLen: exprLen,\n filterKey: entry.key,\n description: '日期格式化',\n type: '函数',\n })\n } else if (entry.node.type === 'number') {\n for (const fn of NUMBER_FUNCTION_SUGGESTIONS) {\n extras.push({\n path: `${fn.name}(${path})`,\n segment: `${fn.name}(${path})`,\n replaceLen: exprLen,\n filterKey: entry.key,\n description: fn.summary,\n type: '函数',\n })\n }\n }\n return extras\n}\n\n/** 子键候选(路径候选 + 按形状分桶的调用插入候选);exprLen = 整段 expr 长度 */\nfunction childCandidates(entry: ExpressionSchemaChildEntry, prefix: string, exprLen: number, withBuckets: boolean): ExpressionCandidate[] {\n const base = childCandidate(entry, prefix)\n if (!withBuckets) return [base]\n return [base, ...callCandidatesFor(entry, base.path, exprLen)]\n}\n\n/** 函数名候选(spec ⑨:已知名单只喂补全建议——`名字()` 插入 + 光标落括号内) */\nfunction functionCandidates(): ExpressionCandidate[] {\n return EXPRESSION_FUNCTIONS.map((fn) => ({\n path: fn.signature,\n segment: `${fn.name}()`,\n cursorBack: 1,\n description: fn.summary,\n type: '函数',\n }))\n}\n\n/** date 格式串快捷项(spec §3.4:具名预设 + 中文模板,写即所得) */\nfunction dateFormatCandidates(): ExpressionCandidate[] {\n return DATE_FORMAT_SUGGESTIONS.map((suggestion) => ({\n path: suggestion.segment,\n segment: suggestion.segment,\n description: suggestion.description,\n type: '格式',\n }))\n}\n\n/** 上下文头部候选:裸键(声明序)+ $root;行上下文追加 row/$index(结构头,踏脚石);open 信号随根节点透出 */\nfunction headCandidates(\n schema: ExpressionSchemaNode,\n context: ExpressionContextKind,\n rowSchema: ExpressionSchemaNode | null | undefined,\n exprLen: number,\n withBuckets: boolean,\n): { candidates: readonly ExpressionCandidate[]; open?: boolean } {\n const view = schemaChildEntries(schema)\n const candidates = view.entries.flatMap((entry) => childCandidates(entry, '', exprLen, withBuckets))\n candidates.push({ path: '$root', segment: '$root' })\n if (context === 'row') {\n candidates.push(rowSchema ? { path: 'row', segment: 'row', ...candidateMeta(rowSchema) } : { path: 'row', segment: 'row' })\n candidates.push({ path: '$index', segment: '$index' })\n }\n return view.open === true ? { candidates, open: true } : { candidates }\n}\n\n/**\n * 调用槽位路由(工单 12,spec ⑨):expr 已是调用形态(⑦ 分类)时按光标槽位\n * 分派建议面。结构红标归扫描面(fragmentSyntaxError),此处只产候选:\n * - 首参路径槽:字段建议复用路径枚举(**关桶/函数建议**——调用不嵌套,参数位只\n * 该写字段);槽文本语法残缺/行外 row. 等枚举不出 = 空候选,不阻断不标红。\n * - date 字符串槽(第二参起):具名预设 + 中文模板快捷项(spec §3.4)。\n * - 其余槽位(数字面/闭合后/嵌套调用形态):空候选。\n */\nfunction callSlotResult(\n schema: ExpressionSchemaNode,\n query: ExpressionCandidateQuery,\n): ExpressionCandidateResult {\n const slot = expressionCallSlotAtEnd(query.expr)\n if (slot === null) {\n return { ok: true, prefix: '', partial: '', candidates: [] }\n }\n if (slot.stringSlot && slot.index >= 1 && slot.name === 'date') {\n return { ok: true, prefix: '', partial: slot.text, candidates: dateFormatCandidates() }\n }\n if (slot.index === 0 && !isCallFragment(slot.text)) {\n const sub = enumeratePathCandidates(schema, query, slot.text, false)\n if (!sub.ok) return { ok: true, prefix: '', partial: '', candidates: [] }\n return sub\n }\n return { ok: true, prefix: '', partial: '', candidates: [] }\n}\n\n/**\n * 枚举当前片段表达式的合法候选(工单 12 起入口按 ⑦ 分类分流:调用形态走\n * callSlotResult 槽位路由,路径形态走本函数既有规则表分派——见模块头注;\n * 分支不可达/节点为叶子一律空候选,不抛错。allowCallSuggestions = 函数名建议与\n * 建议分桶的开关:调用参数位递归关闭——调用不嵌套、参数位只该写字段)。\n */\nfunction enumeratePathCandidates(\n schema: ExpressionSchemaNode,\n query: ExpressionCandidateQuery,\n expr: string,\n allowCallSuggestions: boolean,\n): ExpressionCandidateResult {\n if (schema === null) {\n return { ok: true, prefix: '', partial: '', candidates: [] }\n }\n\n // 单个尾点 = 补全点标记(工单 01 备注);孤点前缀无合法续写\n const hadTrailingDot = expr.endsWith('.')\n const stripped = hadTrailingDot ? expr.slice(0, -1) : expr\n if (stripped === '') {\n if (expr === '') {\n return {\n ok: true,\n prefix: '',\n partial: '',\n ...headCandidates(schema, query.context, query.rowSchema, expr.length, false),\n }\n }\n return { ok: false, reason: 'expression_syntax_error' }\n }\n\n const parsed = parseExpressionPath(stripped)\n if (!parsed.ok) {\n return { ok: false, reason: 'expression_syntax_error' }\n }\n\n const { head, rest } = parsed\n // 前缀路径与正在输入的段:尾点态全路径即前缀;否则尾段是 partial、其余是前缀;\n // 余段为空 = 头部本身在输入中(前缀回落到上下文头部层)\n let prefixPath: readonly string[]\n let partial: string\n if (hadTrailingDot) {\n prefixPath = [headName(head), ...rest]\n partial = ''\n } else if (rest.length > 0) {\n prefixPath = [headName(head), ...rest.slice(0, -1)]\n partial = rest[rest.length - 1] as string\n } else {\n prefixPath = []\n partial = headName(head)\n }\n\n // 桶候选随字段收敛出现(工单 11 ②「选字段后」):partial 非空 = 正在收敛到\n // 某字段;空 expr / 尾点未过滤位保持既有列表零噪音\n const withBuckets = allowCallSuggestions && partial !== ''\n\n if (prefixPath.length === 0) {\n const view = headCandidates(schema, query.context, query.rowSchema, expr.length, withBuckets)\n // 函数名候选(spec ⑨「片段以名字+( 形态开头时建议函数名」):非空裸名位给,\n // 空 expr / 尾点下钻位不给(字段列表零噪音;名单面只喂建议不校验)\n const fns = allowCallSuggestions && expr !== '' ? functionCandidates() : []\n return {\n ok: true,\n prefix: '',\n partial,\n candidates: [...view.candidates, ...fns],\n ...(view.open === true ? { open: true } : {}),\n }\n }\n\n // 头路由对齐求值器 resolveFragment 的 match 分支。base 与下钻段:\n // 裸键头是根 schema 的真实段(段含头名);$root/row 是虚拟头(base 已是其\n // 指向的节点,段剥头名)\n let base: ExpressionSchemaNode | null\n let segments: readonly string[]\n if (head.kind === 'row') {\n if (query.context !== 'row') {\n return { ok: true, prefix: prefixPath.join('.'), partial, candidates: [] } // row. 行外不给\n }\n base = query.rowSchema ?? null\n segments = prefixPath.slice(1)\n } else if (head.kind === 'root') {\n base = schema\n segments = prefixPath.slice(1)\n } else if (head.name === '$index') {\n // $index 仅行上下文给(头部层已按上下文过滤);标量注入值无子候选\n return { ok: true, prefix: prefixPath.join('.'), partial, candidates: [] }\n } else {\n base = schema // 裸键 = 根键(两种上下文一致,见模块头注)\n segments = prefixPath\n }\n\n const node = base === null ? null : schemaNodeAtPath(base, segments)\n const prefix = prefixPath.join('.')\n if (node === null) {\n return { ok: true, prefix, partial, candidates: [] }\n }\n const view = schemaChildEntries(node)\n return {\n ok: true,\n prefix,\n partial,\n candidates: view.entries.flatMap((entry) => childCandidates(entry, prefix, expr.length, withBuckets)),\n ...(view.open === true ? { open: true } : {}),\n }\n}\n\nexport function enumerateExpressionCandidates(\n schema: ExpressionSchemaNode | null,\n query: ExpressionCandidateQuery,\n): ExpressionCandidateResult {\n if (schema === null) {\n return { ok: true, prefix: '', partial: '', candidates: [] }\n }\n // ⑦ 分类分流(工单 12):调用形态前缀 → 光标槽位路由;否则既有路径枚举\n if (isCallFragment(query.expr)) {\n return callSlotResult(schema, query)\n }\n return enumeratePathCandidates(schema, query, query.expr, true)\n}\n","/**\n * rowsPath(取行路径)候选枚举器 + 起点三分流判别(rows-path-completion 工单 01,\n * spec §2 候选规则 + §4.1 落位 D9)。\n *\n * 候选规则逐条导出自水合语义(CanvasHydrator::hydrateTemplateNode,只读对齐):\n * rowsPath 点路径自起点 schema 逐段下钻,终点必须是行数组——\n * - object 子段 = 可继续下钻的中间站候选(无徽标数据);array 子段(type 声明为\n * array 或声明了 items)= 合法终点候选,type 徽标数据恒 'array'(「行数组」展示\n * 文案归后续组装面,浮层零改动);标量子段不出候选(怂恿无效路径即怂恿\n * rows_path_invalid)。\n * - 数组内不下钻:具名段穿数组在水合端无意义(navigateRows 对列表键不命中),\n * 完成段任一落点为数组/标量 → null——数组之下不再产出任何候选(「枚举为空」\n * 以走不通同款收口:无补全态、浮层关闭、手输不受阻不阻断),与声明漂移/缺失\n * 同款,全程不抛错。\n * - open 节点(additionalProperties: true):open 信号随结果透出(信号面与候选\n * 空否无关,对齐 schemaChildEntries 的 D10 面),供浮层渲染占位提示行。\n * - 候选给当前层全量合法子项、不按 partial 预过滤(前缀过滤归工单 02 浮层面,与\n * 表达式枚举器分工同款);单尾点 = 剥点列全量子段;partial 恒为输入串后缀。\n * - 元信息:path(起点相对完整点路径)+ description/title 两字段分离透传(展示\n * 回落 description ?? title 归浮层)+ type 徽标数据。\n *\n * 与 expressionCandidates(content-completion D9)的分叉点:那里数组节点收敛为\n * 叶子(具名段下钻数组在求值器是静默空串——缺字段信号),本题以数组为合法终点\n * ——语义相反,故自实现逐层枚举、**不复用 schemaChildEntries**;下钻组合现成件\n * schemaNodeAtPath + 节点判别(isArrayNode/isScalarNode)。\n *\n * 起点三分流判别(D3):按选中路径位置定枚举起点 schema——无 template 祖先\n * (根层表;建表表单同款恒根起点,不经此判别件)= 载荷根 schema;有 template\n * 祖先 = 沿路径每个 template 段行相对递归 resolveRowSchema(嵌套表自外层行取数,\n * hydrateTemplateNode 同门;判别思路对齐 editor-vue expressionContext 的\n * rowSchemaAlongPath,缺省面相反:表达式面无祖先给 null 表根上下文,本面无祖先\n * 给根 schema 表根起点,故独立落件不改 expressionContext);任一层解析不出\n * (rowsPath 无 items 声明/漂移/途经非表)→ null 降级无候选。\n *\n * 全模块纯函数、零 DOM;层内依赖仅 expressionSchema / expressionCandidates\n * (候选类型面)/ layerPath。\n */\nimport type { Canvas } from '@hankchen/canvas'\n\nimport type { ExpressionCandidate } from './expressionCandidates'\nimport {\n resolveRowSchema,\n schemaNodeAtPath,\n type ExpressionSchemaNode,\n} from './expressionSchema'\nimport { resolveLayer, type LayerPath } from './layerPath'\n\n/**\n * 一条 rowsPath 候选(面同 ExpressionCandidate → 结构兼容 editor-vue 的\n * CompletionItem,浮层零改动复用)。\n */\nexport type RowsPathCandidate = ExpressionCandidate\n\n/** 一次枚举结果(partial/candidates 契约同表达式面 + open 信号);null = 无补全态 */\nexport interface RowsPathCandidateResult {\n /** 正在输入的未完整段('' = 空输入/单尾点态);恒为输入串后缀 */\n partial: string\n /** 当前层全量合法子项(不按 partial 预过滤——前缀过滤归浮层) */\n candidates: readonly RowsPathCandidate[]\n /**\n * 开放映射信号(D10 同款):枚举落点标 open(additionalProperties: true)时为\n * true,非 open 恒缺省——浮层渲染「动态字段,键由模板定义」占位提示行,信号\n * 在场时候选空也不关浮层。\n */\n open?: boolean\n}\n\n/** 节点判别:type 声明 array 或声明 items 即数组节点 = rowsPath 合法终点 */\nfunction isArrayNode(node: ExpressionSchemaNode): boolean {\n return node.type === 'array' || node.items !== undefined\n}\n\n/** 节点判别:无结构声明也无开放映射 = 标量节点(水合端不可导航) */\nfunction isScalarNode(node: ExpressionSchemaNode): boolean {\n return !isArrayNode(node) && node.properties === undefined && node.open !== true\n}\n\n/** 一条候选的元信息组装:终点数组徽标数据恒 'array'(展示文案不在此层定) */\nfunction childCandidate(key: string, node: ExpressionSchemaNode, prefix: string): RowsPathCandidate {\n const candidate: RowsPathCandidate = {\n path: prefix === '' ? key : `${prefix}.${key}`,\n segment: key,\n }\n if (node.description !== undefined) candidate.description = node.description\n if (node.title !== undefined) candidate.title = node.title\n if (isArrayNode(node)) candidate.type = 'array'\n return candidate\n}\n\n/**\n * 一层的合法子项候选(声明序):object/open 子段 = 中间站(无徽标数据);array\n * 子段 = 终点(type 'array');标量子段不出候选。open 信号随落点透出(与候选\n * 空否无关)。\n */\nfunction levelCandidates(\n base: ExpressionSchemaNode,\n prefix: string,\n): { candidates: readonly RowsPathCandidate[]; open?: boolean } {\n const candidates: RowsPathCandidate[] = []\n if (base.properties !== undefined) {\n for (const [key, child] of base.properties) {\n if (isScalarNode(child)) continue\n candidates.push(childCandidate(key, child, prefix))\n }\n }\n return base.open === true ? { candidates, open: true } : { candidates }\n}\n\n/**\n * 枚举当前输入串的合法 rowsPath 候选。起点 schema null(判别件降级态)直接无\n * 候选;完成段任一落点为数组/标量/缺失 → null(无补全态,不抛错——手输不受\n * 阻);枚举落点为对象层 → 该层全量合法子项。\n */\nexport function enumerateRowsPathCandidates(\n startSchema: ExpressionSchemaNode | null,\n input: string,\n): RowsPathCandidateResult | null {\n if (startSchema === null) return null\n\n // 单尾点 = 补全点标记:剥点列全量子段(partial 空);其余尾段即 partial\n const segments = input.split('.')\n const partial = input.endsWith('.') ? '' : (segments[segments.length - 1] as string)\n const completed = segments.slice(0, -1)\n\n let base = startSchema\n for (const segment of completed) {\n if (isArrayNode(base)) return null // 数组内不下钻:具名段穿数组在水合端无意义\n const child = schemaNodeAtPath(base, [segment])\n if (child === null) return null // 声明漂移/缺失/标量落点:无补全态不抛错\n base = child\n }\n if (isArrayNode(base) || isScalarNode(base)) return null // 枚举落点非对象层(终点数组之下无下钻)\n\n const view = levelCandidates(base, completed.join('.'))\n return {\n partial,\n candidates: view.candidates,\n ...(view.open === true ? { open: true } : {}),\n }\n}\n\n/**\n * rowsPath 编辑起点的三分流判别(D3):无 template 祖先 → 载荷根 schema(根层\n * 表;建表表单恒根起点同款);有 template 祖先 → 沿选中路径每个 template 段行\n * 相对递归 resolveRowSchema(首个对根 schema、其后对当前行 schema),返回最终\n * 行 schema;任一层解析不出(rowsPath 无 items 声明/漂移/途经非 TableLayer)→\n * null 降级无候选。rootSchema null(声明未注入/被拒)→ null;doc/path 缺席 →\n * 根起点兜底(画布级判定兜底一致)。\n */\nexport function resolveRowsPathStartSchema(\n doc: Canvas | null,\n path: LayerPath | null,\n rootSchema: ExpressionSchemaNode | null,\n): ExpressionSchemaNode | null {\n if (rootSchema === null) return null\n if (doc === null || path === null) return rootSchema\n let current: ExpressionSchemaNode | null = null\n for (let i = 0; i < path.length; i += 1) {\n if (path[i] !== 'template') continue\n const table = resolveLayer(doc, path.slice(0, i) as LayerPath)\n if (table === null || table.type !== 'TableLayer') return null\n current = resolveRowSchema(current ?? rootSchema, table.rowsPath)\n if (current === null) return null\n }\n return current ?? rootSchema\n}\n","/**\n * 文本布局策略的 memo 包装(canvas-web-render-perf 工单 02):把注入缝的\n * TextLayoutPolicies(断行器 + 度量器工厂)包上引用级缓存——全画布重绘对未变图层\n * 反复求布局(textLines 断行 / layerWidth 自然宽逐段度量)是逐键开销链的大头,\n * immer 结构共享保证未变图层的 (text, width, font, fontSize) 键稳定命中,逐键只\n * 重算被编辑的那一层。\n *\n * 键域与缓存:\n * - 断行:font \\0 fontSize \\0 width \\0 text → 行数组(同键同引用返回);\n * - 度量:font \\0 fontSize \\u0000 text → 宽(measure 纯函数,键即输入)。\n * 两缓存均 LRU(命中重插刷新位次),容量有界(缺省 512 / 2048,可配)——被编辑层\n * 的历史键随容量上限自然淘汰,长会话不无界增长。\n *\n * 缺省分量回落 core 缺省实现并同样被 memo(断行 = createUax14LineBreaker()、度量 =\n * heuristicMeasurerFactory)——宿主只注入 measureText 工厂时(playground 先例)断行\n * 缓存照样生效。核心包(canvas)零改动:本模块纯包装注入缝,语义与未包装完全\n * 一致(wire 零变化,Go/PHP 不跟进)。\n *\n * 上下文传递:断行器签名 (text, width, measurer) 不见 font/fontSize——本包装的\n * wrappedFactory 求值时把 (font, fontSize) 记进闭包上下文,wrappedBreaker 用它组键;\n * 布局消费恒为「先 factory 后 breaker」的同同步序列(layout.ts textLines/layerWidth),\n * 上下文读取因此总对应当前图层。\n */\nimport {\n createUax14LineBreaker,\n heuristicMeasurerFactory,\n type LineBreaker,\n type TextLayoutPolicies,\n type TextMeasurerFactory,\n} from '@hankchen/canvas'\n\nexport interface MemoTextPoliciesOptions {\n /** 断行缓存容量(键数),缺省 512 */\n readonly maxLineEntries?: number\n /** 度量缓存容量(键数),缺省 2048 */\n readonly maxMeasureEntries?: number\n}\n\nconst DEFAULT_LINE_ENTRIES = 512\nconst DEFAULT_MEASURE_ENTRIES = 2048\n\n/** LRU 缓存:命中重插刷新位次,超容逐最旧(Map 插入序即位次) */\nclass LruCache<V> {\n private readonly entries = new Map<string, V>()\n constructor(private readonly capacity: number) {}\n\n get(key: string): V | undefined {\n const hit = this.entries.get(key)\n if (hit !== undefined) {\n this.entries.delete(key)\n this.entries.set(key, hit)\n }\n return hit\n }\n\n put(key: string, value: V): void {\n if (this.entries.size >= this.capacity) {\n const oldest = this.entries.keys().next().value\n if (oldest !== undefined) this.entries.delete(oldest)\n }\n this.entries.set(key, value)\n }\n}\n\nexport function memoizeTextPolicies(\n policies: TextLayoutPolicies = {},\n options: MemoTextPoliciesOptions = {},\n): TextLayoutPolicies {\n const linesCache = new LruCache<string[]>(options.maxLineEntries ?? DEFAULT_LINE_ENTRIES)\n const measureCache = new LruCache<number>(options.maxMeasureEntries ?? DEFAULT_MEASURE_ENTRIES)\n // 当前 (font, fontSize) 上下文:wrappedFactory 求值时刷新,wrappedBreaker 组键消费\n let context = { font: '', fontSize: 0 }\n\n const measureMemoFactory: TextMeasurerFactory = (font, fontSize) => {\n context = { font, fontSize }\n const base = (policies.measurerFactory ?? heuristicMeasurerFactory)(font, fontSize)\n return {\n measure(text: string): number {\n const key = `${font}\\u0000${fontSize}\\u0000${text}`\n const hit = measureCache.get(key)\n if (hit !== undefined) return hit\n const width = base.measure(text)\n measureCache.put(key, width)\n return width\n },\n }\n }\n\n const lineBreakerMemo: LineBreaker = (text, width, measurer) => {\n const key = `${context.font}\\u0000${context.fontSize}\\u0000${width}\\u0000${text}`\n const hit = linesCache.get(key)\n if (hit !== undefined) return hit\n const lines = (policies.lineBreaker ?? createUax14LineBreaker())(text, width, measurer)\n linesCache.put(key, lines)\n return lines\n }\n\n return { lineBreaker: lineBreakerMemo, measurerFactory: measureMemoFactory }\n}\n","/**\n * 资源物化状态的内核切片形态与图片层占位态判定(placeholder-padding-hint 工单 02)。\n *\n * 物化状态住内核 store 的 ui 分支(红线 3 的编辑器延伸:物化状态永不写 graph),\n * 由宿主从渲染端物化状态机的散列键切片桥接注入——整体替换、内容等短路。键取\n * 图片 src 原串:键反解(散列键 → src)在宿主桥完成,内核零散列依赖。\n *\n * 占位态判定是「padding 调了看不到像素变化」的内核权威面:图片层内容绘制发生在\n * drawImage 分支,凡 drawImage 被跳过的层 padding 与像素无关(排查记录\n * canvas-web/layer-padding-research.md 根因 3)。四种未绘制形态:\n * ① 表达式标记图片——设计态预览把 src 置 null(preview.ts 占位盒语义),编辑器最常见;\n * ② 静态空引用/物化未达 done——render 源引用 null 只画盒、后端查不到已物化位图\n * 只画盒(pending/failed 同族);无记录 = 不可知,不假报(宿主未桥接不误报);\n * ③ 声明尺寸为零的自适应图片(解码清零)与极端 padding——内容盒 ≤ 0,后端\n * drawImage ≤0 防护恒跳过。判定取内容盒条件本身(忠实渲染面),auto 标志只是\n * 清零的常见来路(编辑器内打开 auto 不清零声明值,按标志判会误报)。\n *\n * 纯函数、零 DOM;QR/文本层占位不在本判定面(工单范围是图片层)。\n */\nimport { contentHeight, contentWidth } from '@hankchen/canvas'\nimport type { Layer } from '@hankchen/canvas'\n\n/** 单条资源的物化态(与渲染端物化状态机的三态同构,宿主桥按值透传) */\nexport type ResourceStatus = 'pending' | 'done' | 'failed'\n\n/**\n * 资源物化状态切片(store ui 分支 `resourceStatuses` 的形态):key = 图片 src\n * 原串,value = 物化态。整体替换语义,只住会话态——不进历史、不写 graph、\n * wire 零键;openDocument 换文档重置(资源态描述当次文档)。\n */\nexport type ResourceStatusMap = Readonly<Record<string, ResourceStatus>>\n\n/**\n * 图片层设计态内容未绘制(占位态)判定:命中即「当前层内容未绘制,padding 不\n * 影响预览」。非图片层恒 false;statuses 缺省/无记录时物化维度不参与(不可知\n * 不假报——headless 用法与未桥接宿主的静态可加载层不误报)。\n */\nexport function isImageContentUndrawn(layer: Layer, statuses?: ResourceStatusMap): boolean {\n if (layer.type !== 'ImageLayer') return false\n // ① 标记态文档 src 恒镜像表达式原文(非可物化引用),预览视图置空走占位盒\n if (layer.expression !== null) return true\n // ② 静态空引用:render 源引用 null 只画盒(未物化/未加载占位语义同族)\n if (layer.src === null) return true\n // ③ 内容盒 ≤ 0:后端 drawImage 的 ≤0 防护恒跳过(自适应解码清零/极端 padding)\n if (contentWidth(layer) <= 0 || contentHeight(layer) <= 0) return true\n // ② 物化维度:宿主桥接切片里该 src 未达 done(pending/failed = 内容未绘制)\n if (statuses !== undefined) {\n const status = statuses[layer.src]\n if (status !== undefined && status !== 'done') return true\n }\n return false\n}\n","/**\n * @hankchen/canvas-editor headless 内核公共出口。\n *\n * src 按 session(会话门面/store/快捷键)→ spatial(视口几何/拾取/滚轮)→\n * editing(剪贴板/图层/表格/字体/上传)→ shared(layerPath 寻址原语)分层,\n * 下层禁止反向引用上层(dependency-cruiser 域隔离规则 editor-*-isolation 锁定)。\n * 内核只保留根 barrel 单出口:EditorSession 门面是宿主唯一 API,域目录是\n * 内部组织,不开子路径导出,避免宿主绕过门面拼装内核。\n * 红线:内核不依赖任何 UI 绑定层(Vue/React),无 DOM lib,测试全部在\n * Node 无 DOM 环境运行。\n */\nexport const PACKAGE_NAME = '@hankchen/canvas-editor' as const\n\nexport * from './session'\nexport * from './spatial'\nexport * from './editing'\nexport * from './shared'\n// 领域常量与类型的公共再出口:绑定层(editor-vue)按红线不得直连 canvas,\n// 字段描述注册表等消费面从这里取(工单 09 起)\nexport {\n ANCHORS,\n HORIZONTAL_ALIGNS,\n LAYER_TYPES,\n VERTICAL_ALIGNS,\n type Anchor,\n type Border,\n type BorderSide,\n type HorizontalAlign,\n type LayerType,\n type Padding,\n type VerticalAlign,\n} from '@hankchen/canvas'\nexport type {\n Canvas,\n Layer,\n LayerBox,\n TableLayer,\n TableCellLayer,\n TableRowLayer,\n TextLayer,\n} from '@hankchen/canvas'\n// 纯函数的公共再出口:文本编辑 overlay 的字体族解析(工单 11)——与 loadCanvasFont\n// 同一派生,编辑中 textarea 与内容层 canvas 呈现同一字体;绑定层经内核取用\nexport { canvasFontCssFamily } from '@hankchen/canvas-browser-renderer'\n"],"mappings":"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","names":["lineHeightPx","resolveChildAt","resolveLayerBox","child","resolveChildAt","resolveLayerBox","resolveLayerBox","anchorOffset","layerHeight","start","phpTrim","index","layerHeight","anchorOffset","lineHeightPx","view","childCandidate"]}
|