@hankchen/canvas-editor 0.7.1 → 0.9.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 CHANGED
@@ -1,5 +1,5 @@
1
- import { LayerBox, Canvas, TextLayoutPolicies, Layer, LayerType, TableLayer, RenderBackend, Padding, HorizontalAlign, VerticalAlign, Anchor, TableRowLayer, TableCellLayer, TableRowTemplateLayer } from '@hankchen/canvas';
2
- export { ANCHORS, Anchor, Border, BorderSide, Canvas, HORIZONTAL_ALIGNS, HorizontalAlign, LAYER_TYPES, Layer, LayerBox, LayerType, Padding, TableCellLayer, TableLayer, TableRowLayer, TextLayer, VERTICAL_ALIGNS, VerticalAlign } from '@hankchen/canvas';
1
+ import { LayerBox, Canvas, TextLayoutPolicies, Layer, LayerType, TableLayer, RenderBackend, Padding, HorizontalAlign, VerticalAlign, CanvasBackground, Anchor, TableRowLayer, TableCellLayer, TableRowTemplateLayer } from '@hankchen/canvas';
2
+ export { ANCHORS, Anchor, Border, BorderSide, Canvas, CanvasBackground, HORIZONTAL_ALIGNS, HorizontalAlign, LAYER_TYPES, Layer, LayerBox, LayerType, Padding, TableCellLayer, TableLayer, TableRowLayer, TextLayer, VERTICAL_ALIGNS, VerticalAlign } from '@hankchen/canvas';
3
3
  import { Draft, Patch } from 'immer';
4
4
  export { canvasFontCssFamily } from '@hankchen/canvas-browser-renderer';
5
5
 
@@ -1471,6 +1471,11 @@ interface TextEditLayout {
1471
1471
  * 越过死区才插入副本,死区内抬手退化为普通点选。
1472
1472
  */
1473
1473
  declare const ALT_DRAG_DEAD_ZONE_SCREEN_PX = 4;
1474
+ /**
1475
+ * 底图转换不可行的原因(canvas-background 工单 05,右键菜单置灰 title 分轨;
1476
+ * null = 可转换)。locked 只由会话查询口产出(ui 分支态,纯函数管不着)。
1477
+ */
1478
+ type BaseImageConversionBlock = 'not-head-root' | 'not-image' | 'not-covering' | 'hidden' | 'expression-marked' | 'no-image-src' | 'locked';
1474
1479
  interface EditorSessionOptions {
1475
1480
  scheduleFrame: FrameScheduler;
1476
1481
  /** 缩放范围(可配置,缺省 5%–800%) */
@@ -2090,8 +2095,38 @@ declare class EditorSession {
2090
2095
  * commitTextEdit 先清会话再落库,天然不复入接管。
2091
2096
  */
2092
2097
  private takeoverDraftIfNeeded;
2093
- /** 更新画布级字段(未选中图层时面板的宽/高写入口);键由类型收窄为 width/height */
2098
+ /**
2099
+ * 更新画布级字段(未选中图层时面板的写入口):width/height 数值;background
2100
+ * 整对象一步事务(canvas-background 工单 04)——面板各分量提交折算回整对象,
2101
+ * 归一经 normalizeCanvasBackground(空串分量归 null、两分量皆空 = 清背景 null,
2102
+ * 镜像解码语义;条件写键归 encode);结构相同的整对象空转(immer 引义换新
2103
+ * 不产生历史步,数值键的零变更语义同门)。
2104
+ */
2094
2105
  updateCanvasProp(key: 'width' | 'height', value: number, options?: TransactOptions): void;
2106
+ updateCanvasProp(key: 'background', value: CanvasBackground | null, options?: TransactOptions): void;
2107
+ /**
2108
+ * 底图转换查询(canvas-background 工单 05,右键菜单门态唯一权威):路径处
2109
+ * 图层距「转为画布背景」的阻塞原因,null = 可转换。文档面条件见
2110
+ * baseImageConversionBlockInDoc(豁免同门),此处叠加锁定子树(删除面同门
2111
+ * ——转换含删层,锁定层不出路)。无文档/无路径归 not-head-root。
2112
+ */
2113
+ baseImageConversionBlock(path: LayerPath | null): BaseImageConversionBlock | null;
2114
+ /**
2115
+ * 底图右键命中解析(canvas-background 工单 05,豁免的命中缝):命中豁免使
2116
+ * 头根覆盖图片层的自身盒退出正常命中面,右键菜单却要能选中它(「转为画布
2117
+ * 背景」的入口前提)——宿主在命中未中时按此兜底解析转换候选,选择落地由
2118
+ * 宿主决定(纯查询零副作用)。预览视图上解析(hitTest 同门)并映射回文档
2119
+ * 身份;左键点选路径不经过这里,豁免行为零改动。
2120
+ */
2121
+ baseImageAt(sceneX: number, sceneY: number): LayerPath | null;
2122
+ /**
2123
+ * 转为画布背景(canvas-background 工单 05,存量底图出路):选中头根图片层
2124
+ * 的图片引用写入画布背景 image 分量 + 删除该图层,单事务一步历史、整体撤销
2125
+ * (背景与图层都还原)。条件经 baseImageConversionBlock 同一门(违例静默
2126
+ * 空转,无历史步);既有背景色保留(只写 image 分量——先色后图语义不破,
2127
+ * 已有背景图被垫底层的引用接管,同一门下二者本就是同一视觉位)。
2128
+ */
2129
+ convertSelectionToBackground(): boolean;
2095
2130
  /**
2096
2131
  * 注入数据源 schema 声明(载荷形态,D1 钉定:声明即 compile(canvas, dataset)
2097
2132
  * 收到的 data 载荷形状,根上下文候选 = 载荷顶层键)。声明经
@@ -2441,6 +2476,15 @@ interface HitTestOptions {
2441
2476
  */
2442
2477
  readonly lockedPaths?: readonly LayerPath[];
2443
2478
  }
2479
+ /**
2480
+ * 全幅判定:底层自身盒 ⊇ 画布矩形(负溢出照算),与视口/缩放无关。
2481
+ * spec「渲染与交互」2026-09-27 拍板的底图豁免(Fabric backgroundImage 不入命中图、
2482
+ * Konva listening(false) 同构):只作用于数组头根层——「数组按 priority 降序、
2483
+ * 头先画垫底」的叠放不变量决定了它是唯一的视觉垫底层。
2484
+ * 双消费者(canvas-background 工单 05):命中豁免与「转为画布背景」转换门同门——
2485
+ * 判定一处收口,两处语义永不漂移。
2486
+ */
2487
+ declare function coversCanvas(box: LayerBox, canvasWidth: number, canvasHeight: number): boolean;
2444
2488
  /**
2445
2489
  * 场景坐标命中:返回视觉最上被中图层的路径(未中返回 null)。
2446
2490
  * 隐藏根层(layer-panel-ux 工单 10)与锁定根层(canvas-web-layer-lock 工单 01)
@@ -2448,6 +2492,23 @@ interface HitTestOptions {
2448
2492
  * 被锁定即不可点选,命中穿透其下方的层。
2449
2493
  */
2450
2494
  declare function hitTest(canvas: Canvas, sceneX: number, sceneY: number, policies?: TextLayoutPolicies, options?: HitTestOptions): LayerPath | null;
2495
+ /**
2496
+ * 头根层盒解析(底图判定共用来料):数组头缺失返回 null。命中豁免与「转为画布
2497
+ * 背景」转换门共用此唯一进料(同层、同参盒解析),两处几何语义永不漂移
2498
+ * (canvas-background 工单 05)。
2499
+ */
2500
+ declare function headRootBox(canvas: Canvas, policies?: TextLayoutPolicies): {
2501
+ layer: Layer;
2502
+ box: LayerBox;
2503
+ } | null;
2504
+ /**
2505
+ * 底图右键命中解析(canvas-background 工单 05):豁免使头根覆盖图片层的自身盒
2506
+ * 退出正常命中面,右键菜单却要能选中它(「转为画布背景」的入口缝)——命中未中
2507
+ * 时按此解析兜底。只解析转换候选:数组头 + 图片层 + 覆盖画布(coversCanvas
2508
+ * 同门)+ 可见 + 未锁定;表达式标记与无图引用照常解析(菜单要能选中它置灰说明)。
2509
+ * 纯查询零副作用——选择落地由调用方决定。
2510
+ */
2511
+ declare function baseImageCandidateAt(canvas: Canvas, sceneX: number, sceneY: number, policies?: TextLayoutPolicies, options?: HitTestOptions): LayerPath | null;
2451
2512
 
2452
2513
  /**
2453
2514
  * 画拉建层橡皮筋求位几何(drag-create 工单 01,术语见根 CONTEXT.md「画拉建层」
@@ -3144,6 +3205,16 @@ declare function enumerateExpressionCandidates(schema: ExpressionSchemaNode | nu
3144
3205
  * 给根 schema 表根起点,故独立落件不改 expressionContext);任一层解析不出
3145
3206
  * (rowsPath 无 items 声明/漂移/途经非表)→ null 降级无候选。
3146
3207
  *
3208
+ * 点选器剪枝枚举件(rows-path-picker 工单 01,spec D5/D3):级联点选器每层的
3209
+ * 候选条目——在补全枚举语义上加剪枝谓词 leadsToArray,候选面收敛至「通往行数组
3210
+ * 的路径」:标量子段不出现;对象子段仅当子树内沿 properties 链至少含一个可达
3211
+ * 数组才出现;数组子段 = 终点条目(isArray 标记),不透传 items(数组套数组的
3212
+ * 内层不可达)。无 partial 概念(纯点选无输入期),前缀 = 已积累点路径、逐段
3213
+ * 全量已消费、尾段取子层;空层/漂移/畸形前缀一律返回空数组(空态呈现归 vue
3214
+ * 面)。起点判别不在此重复造:调用方经 resolveRowsPathStartSchema 得起点后再
3215
+ * 进本件。元信息同表达式补全先例(D8):description/title 两字段分离透传,展示
3216
+ * 回落 description ?? title 归呈现层。
3217
+ *
3147
3218
  * 全模块纯函数、零 DOM;层内依赖仅 expressionSchema / expressionCandidates
3148
3219
  * (候选类型面)/ layerPath。
3149
3220
  */
@@ -3166,6 +3237,31 @@ interface RowsPathCandidateResult {
3166
3237
  * 阻);枚举落点为对象层 → 该层全量合法子项。
3167
3238
  */
3168
3239
  declare function enumerateRowsPathCandidates(startSchema: ExpressionSchemaNode | null, input: string): RowsPathCandidateResult | null;
3240
+ /**
3241
+ * 一条 rowsPath 点选器条目(rows-path-picker 工单 01):级联一层的可点项。
3242
+ * 与 RowsPathCandidate 的分叉:无 partial/徽标面,换显式数组终点标记——点选器
3243
+ * 按 isArray 分流「点选提交 / 只可下钻」,不再经「带尾点探一层」推断。
3244
+ */
3245
+ interface RowsPathPickerEntry {
3246
+ /** 提交/下钻的完整点路径(起点相对;点选器 v-model 面就是它) */
3247
+ path: string;
3248
+ /** 本段名(无元信息时的显示回落) */
3249
+ segment: string;
3250
+ /** 显示名注解(D8 两字段分离透传;展示回落 description ?? title 归呈现层) */
3251
+ title?: string;
3252
+ /** 说明(未声明为 undefined) */
3253
+ description?: string;
3254
+ /** 数组终点标记:true = 行数组终点(点选即提交);false = 对象中间站(只可下钻) */
3255
+ isArray: boolean;
3256
+ }
3257
+ /**
3258
+ * 枚举点选器某层的候选条目(rows-path-picker 工单 01):prefix 为已积累点路径,
3259
+ * 逐段全量已消费、尾段取子层('' = 起点层);下钻域规则走 descendToLayer,每层
3260
+ * 条目经 leadsToArray 剪枝至「通往行数组的路径」。起点 schema null / 前缀漂移
3261
+ * (段缺失/标量落点)/ 畸形(双点/尾点/头点)/ 数组落点(数组不展开)→ 空数组,
3262
+ * 全程不抛错——空态呈现归 vue 面(spec D6)。
3263
+ */
3264
+ declare function enumerateRowsPathPickerEntries(startSchema: ExpressionSchemaNode | null, prefix: string): readonly RowsPathPickerEntry[];
3169
3265
  /**
3170
3266
  * rowsPath 编辑起点的三分流判别(D3):无 template 祖先 → 载荷根 schema(根层
3171
3267
  * 表;建表表单恒根起点同款);有 template 祖先 → 沿选中路径每个 template 段行
@@ -3221,4 +3317,4 @@ declare function memoizeTextPolicies(policies?: TextLayoutPolicies, options?: Me
3221
3317
  */
3222
3318
  declare const PACKAGE_NAME: "@hankchen/canvas-editor";
3223
3319
 
3224
- export { ALIGN_CORNER_MARGIN_PX, ALT_DRAG_DEAD_ZONE_SCREEN_PX, ARM_CREATE_LAYER_TYPES, type AlignBoxGeometry, type AlignToCanvasMode, type AlignToCanvasOptions, type ArmCreateShortcutAction, CREATE_DEAD_ZONE_SCREEN_PX, type CanvasEditorState, type CreateGesture, type CreateRectResult, DATE_FORMAT_SUGGESTIONS, DEFAULT_EDITOR_SHORTCUTS, DEFAULT_ZOOM_BOUNDS, type DataSourceSchemaRejection, type DeletedLayerRef, type DocRecipe, type DragGesture, EDITOR_STATE_SCHEMA_VERSION, EXPRESSION_FUNCTIONS, type EditorChange, type EditorPrefsState, EditorSession, type EditorSessionOptions, type EditorShortcutAction, type EditorShortcutBinding, type EditorShortcutGroup, type EditorShortcutInput, EditorStore, type EditorUi, type ExpressionCallArg, type ExpressionCallErrorKind, type ExpressionCallParse, type ExpressionCallSlotContext, type ExpressionCandidate, type ExpressionCandidateQuery, type ExpressionCandidateResult, type ExpressionContextKind, type ExpressionErrorRange, type ExpressionFormatSuggestion, type ExpressionFragmentDiagnostic, type ExpressionFragmentErrorKind, type ExpressionFunctionMeta, type ExpressionPathHead, type ExpressionPathParse, type ExpressionScan, type ExpressionScanFragment, type ExpressionScanLiteral, type ExpressionScanOpen, type ExpressionScanPart, type ExpressionSchemaChildEntry, type ExpressionSchemaChildView, type ExpressionSchemaDiagnostic, type ExpressionSchemaDiagnosticCode, type ExpressionSchemaNode, type ExpressionSchemaParse, type ExpressionSchemaRejectReason, type ExpressionSyntaxError, type FindSession, type FindTextHit, FontCatalog, type FontCatalogEntry, type FrameScheduler, type Guide, type GuideOrientation, type HistoryStep, type HitTestOptions, type InvalidateTarget, type LayerOutlineNode, type LayerOutlineRole, type LayerPath, MAX_HISTORY_STEPS, type MemoTextPoliciesOptions, type MovedSubtreeRef, NUMBER_FUNCTION_SUGGESTIONS, type NextTextStyle, type OverlayPaintArgs, type OverlayPainter, PACKAGE_NAME, PAN_TO_BOX_MARGIN_PX, PASTE_OFFSET_PX, PHP_TRIM_CHARS, type Point, type ProportionalDrivingAxis, RATIO_PRESETS, RESIZE_HANDLES, RESIZE_HANDLE_HIT_PX, RESIZE_MIN_SIZE_PX, type RatioPreset, type RatioPresetId, type Rect, type ResizeAxes, type ResizeBoxGeometry, type ResizeBoxResult, type ResizeHandle, type ResourceStatus, type ResourceStatusMap, type RowsPathCandidate, type RowsPathCandidateResult, SNAP_THRESHOLD_SCREEN_PX, STYLE_FIELD_KEYS_BY_TYPE, type ShortcutCombo, type Size, type SnapAxis, type SnapAxisSource, type SnapBoxGeometry, type SnapPoints, type SnapResolution, type StyleSnapshot, TEXT_PRESETS, type TextEditLayout, type TextEditingSession, type TextPreset, type TextPresetId, type TransactOptions, type UploadFile, type UploadHandler, UploadHandlerMissingError, type UploadImagePlacement, type Viewport, WHEEL_ZOOM_STEP, type WheelInput, type WheelIntent, type ZoomBounds, addRootLayerInDraft, addTableCellInDraft, addTableRowInDraft, addTemplateCellInDraft, alignToCanvasTarget, applyStyleFieldsInDraft, buildLayerOutline, canCopyLayerAt, canonicalizeTableSyncInDraft, captureStyleSnapshot, clampZoom, classifyEditorShortcut, classifyWheel, cloneLayerSubtree, convertTableToRowsInDraft, convertTableToTemplateInDraft, createDefaultLayer, createRubberBandRect, createTemplateTable, defaultNextTextStyle, defaultSizeForRatio, deleteLayerInDraft, enumerateExpressionCandidates, enumerateRowsPathCandidates, expressionCallSlotAtEnd, expressionDiagnostics, expressionFragmentAtCursor, findPresetEntry, fitRect, fitViewport, growRowToCellInDraft, handleResizesHeight, handleResizesWidth, hitTest, insertRootLayerAdjacentInDraft, insertRootLayerInDraft, isCallFragment, isImageContentUndrawn, isLayerPath, isLockedPath, isRootLayerPath, isRootPasteableType, isTemplateSubtreePath, layerBoxByPath, memoizeTextPolicies, moveGuard, moveRootLayerInDraft, moveTableCellInDraft, moveTableCellToRowInDraft, moveTableRowInDraft, moveTableRowToTableInDraft, nextZoomByWheel, normalizeExpressionSchemaSource, panBy, panToBox, parseExpressionCall, parseExpressionPath, parseExpressionSchema, pastePosition, pathStartsWith, pathsEqual, phpTrim, pickNextTextStylePatch, prepareRootPaste, proportionalDrivingAxis, ratioValue, readSpecField, remapPathAfterSplice, replaceHitsInDraft, resizableAxesAt, resizeBox, resizeBoxProportional, resizeHandleAt, resizeHandlePoint, resizeHandlesAt, resizeProportionalSnapPoints, resizeSnapPoints, resolveLayer, resolveRowSchema, resolveRowsPathStartSchema, resolveSnap, resolveSnapPoints, resourcePathDiagnostics, rootLayerOf, scanExpressionFragments, scanTextMatches, sceneToScreen, schemaChildEntries, schemaNodeAtPath, screenToScene, selectionParentPath, setCellAutoHeightInDraft, snapAxesFromBoxes, snapThresholdScene, snapViewportToPhysicalPixels, syncContentIntoCellInDraft, syncRowWidthInDraft, uploadDisplayName, visibleRootBoxes, writeSpecField, zoomAtPoint };
3320
+ export { ALIGN_CORNER_MARGIN_PX, ALT_DRAG_DEAD_ZONE_SCREEN_PX, ARM_CREATE_LAYER_TYPES, type AlignBoxGeometry, type AlignToCanvasMode, type AlignToCanvasOptions, type ArmCreateShortcutAction, type BaseImageConversionBlock, CREATE_DEAD_ZONE_SCREEN_PX, type CanvasEditorState, type CreateGesture, type CreateRectResult, DATE_FORMAT_SUGGESTIONS, DEFAULT_EDITOR_SHORTCUTS, DEFAULT_ZOOM_BOUNDS, type DataSourceSchemaRejection, type DeletedLayerRef, type DocRecipe, type DragGesture, EDITOR_STATE_SCHEMA_VERSION, EXPRESSION_FUNCTIONS, type EditorChange, type EditorPrefsState, EditorSession, type EditorSessionOptions, type EditorShortcutAction, type EditorShortcutBinding, type EditorShortcutGroup, type EditorShortcutInput, EditorStore, type EditorUi, type ExpressionCallArg, type ExpressionCallErrorKind, type ExpressionCallParse, type ExpressionCallSlotContext, type ExpressionCandidate, type ExpressionCandidateQuery, type ExpressionCandidateResult, type ExpressionContextKind, type ExpressionErrorRange, type ExpressionFormatSuggestion, type ExpressionFragmentDiagnostic, type ExpressionFragmentErrorKind, type ExpressionFunctionMeta, type ExpressionPathHead, type ExpressionPathParse, type ExpressionScan, type ExpressionScanFragment, type ExpressionScanLiteral, type ExpressionScanOpen, type ExpressionScanPart, type ExpressionSchemaChildEntry, type ExpressionSchemaChildView, type ExpressionSchemaDiagnostic, type ExpressionSchemaDiagnosticCode, type ExpressionSchemaNode, type ExpressionSchemaParse, type ExpressionSchemaRejectReason, type ExpressionSyntaxError, type FindSession, type FindTextHit, FontCatalog, type FontCatalogEntry, type FrameScheduler, type Guide, type GuideOrientation, type HistoryStep, type HitTestOptions, type InvalidateTarget, type LayerOutlineNode, type LayerOutlineRole, type LayerPath, MAX_HISTORY_STEPS, type MemoTextPoliciesOptions, type MovedSubtreeRef, NUMBER_FUNCTION_SUGGESTIONS, type NextTextStyle, type OverlayPaintArgs, type OverlayPainter, PACKAGE_NAME, PAN_TO_BOX_MARGIN_PX, PASTE_OFFSET_PX, PHP_TRIM_CHARS, type Point, type ProportionalDrivingAxis, RATIO_PRESETS, RESIZE_HANDLES, RESIZE_HANDLE_HIT_PX, RESIZE_MIN_SIZE_PX, type RatioPreset, type RatioPresetId, type Rect, type ResizeAxes, type ResizeBoxGeometry, type ResizeBoxResult, type ResizeHandle, type ResourceStatus, type ResourceStatusMap, type RowsPathCandidate, type RowsPathCandidateResult, type RowsPathPickerEntry, SNAP_THRESHOLD_SCREEN_PX, STYLE_FIELD_KEYS_BY_TYPE, type ShortcutCombo, type Size, type SnapAxis, type SnapAxisSource, type SnapBoxGeometry, type SnapPoints, type SnapResolution, type StyleSnapshot, TEXT_PRESETS, type TextEditLayout, type TextEditingSession, type TextPreset, type TextPresetId, type TransactOptions, type UploadFile, type UploadHandler, UploadHandlerMissingError, type UploadImagePlacement, type Viewport, WHEEL_ZOOM_STEP, type WheelInput, type WheelIntent, type ZoomBounds, addRootLayerInDraft, addTableCellInDraft, addTableRowInDraft, addTemplateCellInDraft, alignToCanvasTarget, applyStyleFieldsInDraft, baseImageCandidateAt, buildLayerOutline, canCopyLayerAt, canonicalizeTableSyncInDraft, captureStyleSnapshot, clampZoom, classifyEditorShortcut, classifyWheel, cloneLayerSubtree, convertTableToRowsInDraft, convertTableToTemplateInDraft, coversCanvas, createDefaultLayer, createRubberBandRect, createTemplateTable, defaultNextTextStyle, defaultSizeForRatio, deleteLayerInDraft, enumerateExpressionCandidates, enumerateRowsPathCandidates, enumerateRowsPathPickerEntries, expressionCallSlotAtEnd, expressionDiagnostics, expressionFragmentAtCursor, findPresetEntry, fitRect, fitViewport, growRowToCellInDraft, handleResizesHeight, handleResizesWidth, headRootBox, hitTest, insertRootLayerAdjacentInDraft, insertRootLayerInDraft, isCallFragment, isImageContentUndrawn, isLayerPath, isLockedPath, isRootLayerPath, isRootPasteableType, isTemplateSubtreePath, layerBoxByPath, memoizeTextPolicies, moveGuard, moveRootLayerInDraft, moveTableCellInDraft, moveTableCellToRowInDraft, moveTableRowInDraft, moveTableRowToTableInDraft, nextZoomByWheel, normalizeExpressionSchemaSource, panBy, panToBox, parseExpressionCall, parseExpressionPath, parseExpressionSchema, pastePosition, pathStartsWith, pathsEqual, phpTrim, pickNextTextStylePatch, prepareRootPaste, proportionalDrivingAxis, ratioValue, readSpecField, remapPathAfterSplice, replaceHitsInDraft, resizableAxesAt, resizeBox, resizeBoxProportional, resizeHandleAt, resizeHandlePoint, resizeHandlesAt, resizeProportionalSnapPoints, resizeSnapPoints, resolveLayer, resolveRowSchema, resolveRowsPathStartSchema, resolveSnap, resolveSnapPoints, resourcePathDiagnostics, rootLayerOf, scanExpressionFragments, scanTextMatches, sceneToScreen, schemaChildEntries, schemaNodeAtPath, screenToScene, selectionParentPath, setCellAutoHeightInDraft, snapAxesFromBoxes, snapThresholdScene, snapViewportToPhysicalPixels, syncContentIntoCellInDraft, syncRowWidthInDraft, uploadDisplayName, visibleRootBoxes, writeSpecField, zoomAtPoint };
package/dist/index.js CHANGED
@@ -306,6 +306,19 @@ function hitTest(canvas, sceneX, sceneY, policies, options) {
306
306
  }
307
307
  return null;
308
308
  }
309
+ function headRootBox(canvas, policies) {
310
+ const layer = canvas.layers[0];
311
+ if (!layer) return null;
312
+ return { layer, box: resolveLayerBox2(layer, 0, 0, canvas.width, canvas.height, policies) };
313
+ }
314
+ function baseImageCandidateAt(canvas, sceneX, sceneY, policies, options) {
315
+ const head = headRootBox(canvas, policies);
316
+ if (head === null || head.layer.type !== "ImageLayer" || head.layer.visible === false) return null;
317
+ const lockedPaths = options?.lockedPaths;
318
+ if (lockedPaths !== void 0 && isLockedPath(["layers", 0], lockedPaths)) return null;
319
+ if (!coversCanvas(head.box, canvas.width, canvas.height)) return null;
320
+ return contains(head.box, sceneX, sceneY) ? ["layers", 0] : null;
321
+ }
309
322
 
310
323
  // src/spatial/snap.ts
311
324
  import { resolveLayerBox as resolveLayerBox3 } from "@hankchen/canvas";
@@ -2317,6 +2330,29 @@ var ALT_DRAG_DEAD_ZONE_SCREEN_PX = 4;
2317
2330
  var NUDGE_STEP_PX = 1;
2318
2331
  var NUDGE_COARSE_STEP_PX = 10;
2319
2332
  var nudgeMergeKey = (path) => `nudge:${path.join(":")}`;
2333
+ function normalizeCanvasBackground(value) {
2334
+ if (value === null) return null;
2335
+ const trim = (raw) => typeof raw === "string" && raw.trim() !== "" ? raw : null;
2336
+ const color = trim(value.color);
2337
+ const image = trim(value.image);
2338
+ return color === null && image === null ? null : { color, image };
2339
+ }
2340
+ function sameCanvasBackground(a, b) {
2341
+ if (a === null || b === null) return a === b;
2342
+ return a.color === b.color && a.image === b.image;
2343
+ }
2344
+ function baseImageConversionBlockInDoc(doc, path, policies) {
2345
+ if (path.length !== 2 || path[0] !== "layers" || path[1] !== 0) return "not-head-root";
2346
+ const head = headRootBox(doc, policies);
2347
+ if (head === null) return "not-head-root";
2348
+ if (head.layer.type !== "ImageLayer") return "not-image";
2349
+ if (!coversCanvas(head.box, doc.width, doc.height)) return "not-covering";
2350
+ const layer = head.layer;
2351
+ if (layer.visible === false) return "hidden";
2352
+ if (layer.expression !== null) return "expression-marked";
2353
+ if (layer.src === null || layer.src.trim() === "") return "no-image-src";
2354
+ return null;
2355
+ }
2320
2356
  function clampFindCursor(cursor, count) {
2321
2357
  if (count <= 0) return 0;
2322
2358
  return Math.min(Math.max(cursor, 0), count - 1);
@@ -3660,12 +3696,69 @@ var EditorSession = class {
3660
3696
  if (!paths.some((path) => pathsEqual(path, editing.path))) return;
3661
3697
  this.store.setEditing({ ...editing, draftSeq: (editing.draftSeq ?? 0) + 1 });
3662
3698
  }
3663
- /** 更新画布级字段(未选中图层时面板的宽/高写入口);键由类型收窄为 width/height */
3664
3699
  updateCanvasProp(key, value, options = {}) {
3665
3700
  this.store.transact((draft) => {
3701
+ if (key === "background") {
3702
+ const next = normalizeCanvasBackground(value);
3703
+ const current = draft.background ?? null;
3704
+ if (sameCanvasBackground(current, next)) return;
3705
+ draft.background = next;
3706
+ return;
3707
+ }
3666
3708
  draft[key] = value;
3667
3709
  }, options);
3668
3710
  }
3711
+ /**
3712
+ * 底图转换查询(canvas-background 工单 05,右键菜单门态唯一权威):路径处
3713
+ * 图层距「转为画布背景」的阻塞原因,null = 可转换。文档面条件见
3714
+ * baseImageConversionBlockInDoc(豁免同门),此处叠加锁定子树(删除面同门
3715
+ * ——转换含删层,锁定层不出路)。无文档/无路径归 not-head-root。
3716
+ */
3717
+ baseImageConversionBlock(path) {
3718
+ const doc = this.store.doc;
3719
+ if (!doc || path === null) return "not-head-root";
3720
+ const block = baseImageConversionBlockInDoc(doc, path, this.textPolicies);
3721
+ if (block !== null) return block;
3722
+ return this.isLocked(path) ? "locked" : null;
3723
+ }
3724
+ /**
3725
+ * 底图右键命中解析(canvas-background 工单 05,豁免的命中缝):命中豁免使
3726
+ * 头根覆盖图片层的自身盒退出正常命中面,右键菜单却要能选中它(「转为画布
3727
+ * 背景」的入口前提)——宿主在命中未中时按此兜底解析转换候选,选择落地由
3728
+ * 宿主决定(纯查询零副作用)。预览视图上解析(hitTest 同门)并映射回文档
3729
+ * 身份;左键点选路径不经过这里,豁免行为零改动。
3730
+ */
3731
+ baseImageAt(sceneX, sceneY) {
3732
+ const view = this.previewCanvas;
3733
+ if (!view) return null;
3734
+ const hit = baseImageCandidateAt(view, sceneX, sceneY, this.textPolicies, {
3735
+ lockedPaths: this.store.ui.lockedPaths
3736
+ });
3737
+ if (hit === null) return null;
3738
+ const docPath = viewPathToDocPath(view, hit);
3739
+ return docPath ? docPath : null;
3740
+ }
3741
+ /**
3742
+ * 转为画布背景(canvas-background 工单 05,存量底图出路):选中头根图片层
3743
+ * 的图片引用写入画布背景 image 分量 + 删除该图层,单事务一步历史、整体撤销
3744
+ * (背景与图层都还原)。条件经 baseImageConversionBlock 同一门(违例静默
3745
+ * 空转,无历史步);既有背景色保留(只写 image 分量——先色后图语义不破,
3746
+ * 已有背景图被垫底层的引用接管,同一门下二者本就是同一视觉位)。
3747
+ */
3748
+ convertSelectionToBackground() {
3749
+ const doc = this.store.doc;
3750
+ const path = this.store.ui.selection;
3751
+ if (!doc || path === null) return false;
3752
+ if (this.baseImageConversionBlock(path) !== null) return false;
3753
+ const src = resolveLayer(doc, path).src;
3754
+ this.store.transact((draft) => {
3755
+ const current = normalizeCanvasBackground(draft.background ?? null);
3756
+ draft.background = { color: current?.color ?? null, image: src };
3757
+ deleteLayerInDraft(draft, path);
3758
+ });
3759
+ this.remapPathSlices([], "layers", 0, 0);
3760
+ return true;
3761
+ }
3669
3762
  // ---- 数据源 schema 声明(content-completion 工单 03/08):D2 宿主随会话注入 ----
3670
3763
  /**
3671
3764
  * 注入数据源 schema 声明(载荷形态,D1 钉定:声明即 compile(canvas, dataset)
@@ -4761,13 +4854,29 @@ function isArrayNode(node) {
4761
4854
  function isScalarNode(node) {
4762
4855
  return !isArrayNode(node) && node.properties === void 0;
4763
4856
  }
4857
+ function joinSegment(prefix, key) {
4858
+ return prefix === "" ? key : `${prefix}.${key}`;
4859
+ }
4860
+ function withMeta(target, node) {
4861
+ if (node.description !== void 0) target.description = node.description;
4862
+ if (node.title !== void 0) target.title = node.title;
4863
+ return target;
4864
+ }
4865
+ function descendToLayer(start, segments) {
4866
+ let base = start;
4867
+ for (const segment of segments) {
4868
+ if (isArrayNode(base)) return null;
4869
+ const child = schemaNodeAtPath(base, [segment]);
4870
+ if (child === null) return null;
4871
+ base = child;
4872
+ }
4873
+ return isArrayNode(base) || isScalarNode(base) ? null : base;
4874
+ }
4764
4875
  function childCandidate2(key, node, prefix) {
4765
- const candidate = {
4766
- path: prefix === "" ? key : `${prefix}.${key}`,
4767
- segment: key
4768
- };
4769
- if (node.description !== void 0) candidate.description = node.description;
4770
- if (node.title !== void 0) candidate.title = node.title;
4876
+ const candidate = withMeta(
4877
+ { path: joinSegment(prefix, key), segment: key },
4878
+ node
4879
+ );
4771
4880
  if (isArrayNode(node)) candidate.type = "array";
4772
4881
  return candidate;
4773
4882
  }
@@ -4786,17 +4895,40 @@ function enumerateRowsPathCandidates(startSchema, input) {
4786
4895
  const segments = input.split(".");
4787
4896
  const partial = input.endsWith(".") ? "" : segments[segments.length - 1];
4788
4897
  const completed = segments.slice(0, -1);
4789
- let base = startSchema;
4790
- for (const segment of completed) {
4791
- if (isArrayNode(base)) return null;
4792
- const child = schemaNodeAtPath(base, [segment]);
4793
- if (child === null) return null;
4794
- base = child;
4795
- }
4796
- if (isArrayNode(base) || isScalarNode(base)) return null;
4898
+ const base = descendToLayer(startSchema, completed);
4899
+ if (base === null) return null;
4797
4900
  const view = levelCandidates(base, completed.join("."));
4798
4901
  return { partial, candidates: view };
4799
4902
  }
4903
+ function leadsToArray(node) {
4904
+ if (isArrayNode(node)) return true;
4905
+ if (node.properties === void 0) return false;
4906
+ for (const child of node.properties.values()) {
4907
+ if (leadsToArray(child)) return true;
4908
+ }
4909
+ return false;
4910
+ }
4911
+ function pickerEntry(key, node, prefix) {
4912
+ return withMeta(
4913
+ { path: joinSegment(prefix, key), segment: key, isArray: isArrayNode(node) },
4914
+ node
4915
+ );
4916
+ }
4917
+ function enumerateRowsPathPickerEntries(startSchema, prefix) {
4918
+ if (startSchema === null) return [];
4919
+ const segments = prefix === "" ? [] : prefix.split(".");
4920
+ if (segments.includes("")) return [];
4921
+ const base = descendToLayer(startSchema, segments);
4922
+ if (base === null) return [];
4923
+ const entries = [];
4924
+ if (base.properties !== void 0) {
4925
+ for (const [key, child] of base.properties) {
4926
+ if (!leadsToArray(child)) continue;
4927
+ entries.push(pickerEntry(key, child, prefix));
4928
+ }
4929
+ }
4930
+ return entries;
4931
+ }
4800
4932
  function resolveRowsPathStartSchema(doc, path, rootSchema) {
4801
4933
  if (rootSchema === null) return null;
4802
4934
  if (doc === null || path === null) return rootSchema;
@@ -4930,6 +5062,7 @@ export {
4930
5062
  addTemplateCellInDraft,
4931
5063
  alignToCanvasTarget,
4932
5064
  applyStyleFieldsInDraft,
5065
+ baseImageCandidateAt,
4933
5066
  buildLayerOutline,
4934
5067
  canCopyLayerAt,
4935
5068
  canonicalizeTableSyncInDraft,
@@ -4941,6 +5074,7 @@ export {
4941
5074
  cloneLayerSubtree,
4942
5075
  convertTableToRowsInDraft,
4943
5076
  convertTableToTemplateInDraft,
5077
+ coversCanvas,
4944
5078
  createDefaultLayer,
4945
5079
  createRubberBandRect,
4946
5080
  createTemplateTable,
@@ -4949,6 +5083,7 @@ export {
4949
5083
  deleteLayerInDraft,
4950
5084
  enumerateExpressionCandidates,
4951
5085
  enumerateRowsPathCandidates,
5086
+ enumerateRowsPathPickerEntries,
4952
5087
  expressionCallSlotAtEnd,
4953
5088
  expressionDiagnostics,
4954
5089
  expressionFragmentAtCursor,
@@ -4958,6 +5093,7 @@ export {
4958
5093
  growRowToCellInDraft,
4959
5094
  handleResizesHeight,
4960
5095
  handleResizesWidth,
5096
+ headRootBox,
4961
5097
  hitTest,
4962
5098
  insertRootLayerAdjacentInDraft,
4963
5099
  insertRootLayerInDraft,