@hankchen/canvas-editor 0.5.0 → 0.6.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 +190 -37
- package/dist/index.js +196 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -382,11 +382,22 @@ function resolveSnap(box, axes, threshold) {
|
|
|
382
382
|
|
|
383
383
|
// src/spatial/create.ts
|
|
384
384
|
var CREATE_DEAD_ZONE_SCREEN_PX = 4;
|
|
385
|
-
function createRubberBandRect(type, start, current, axes, threshold) {
|
|
385
|
+
function createRubberBandRect(type, start, current, axes, threshold, ratio) {
|
|
386
386
|
const x = Math.min(start.x, current.x);
|
|
387
387
|
const y = Math.min(start.y, current.y);
|
|
388
|
-
const
|
|
389
|
-
const
|
|
388
|
+
const dx = Math.abs(current.x - start.x);
|
|
389
|
+
const dy = Math.abs(current.y - start.y);
|
|
390
|
+
let width = dx;
|
|
391
|
+
let height = dy;
|
|
392
|
+
if (type === "QrCodeLayer") {
|
|
393
|
+
height = width;
|
|
394
|
+
} else if (ratio) {
|
|
395
|
+
if (dx >= dy) {
|
|
396
|
+
height = Math.round(dx * ratio.h / ratio.w);
|
|
397
|
+
} else {
|
|
398
|
+
width = Math.round(dy * ratio.w / ratio.h);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
390
401
|
const resolution = resolveSnapPoints(
|
|
391
402
|
{ vertical: [x, x + width], horizontal: [y, y + height] },
|
|
392
403
|
axes,
|
|
@@ -435,6 +446,38 @@ function resizeSnapPoints(start, handle, dx, dy) {
|
|
|
435
446
|
if (SOUTH_HANDLES.has(handle)) horizontal.push(start.y + start.height + dy);
|
|
436
447
|
return { vertical, horizontal };
|
|
437
448
|
}
|
|
449
|
+
function proportionalDrivingAxis(handle, dx, dy) {
|
|
450
|
+
const corner = handleResizesWidth(handle) && handleResizesHeight(handle);
|
|
451
|
+
if (corner) return Math.abs(dx) >= Math.abs(dy) ? "width" : "height";
|
|
452
|
+
return handleResizesWidth(handle) ? "width" : "height";
|
|
453
|
+
}
|
|
454
|
+
function resizeBoxProportional(start, handle, reference, driving, dx, dy, minSize = RESIZE_MIN_SIZE_PX) {
|
|
455
|
+
const west = WEST_HANDLES.has(handle);
|
|
456
|
+
const north = NORTH_HANDLES.has(handle);
|
|
457
|
+
let width;
|
|
458
|
+
let height;
|
|
459
|
+
if (driving === "width") {
|
|
460
|
+
width = Math.max(west ? start.width - dx : start.width + dx, minSize);
|
|
461
|
+
height = Math.max(Math.round(width * reference.h / reference.w), minSize);
|
|
462
|
+
} else {
|
|
463
|
+
height = Math.max(north ? start.height - dy : start.height + dy, minSize);
|
|
464
|
+
width = Math.max(Math.round(height * reference.w / reference.h), minSize);
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
x: west ? start.x + start.width - width : start.x,
|
|
468
|
+
y: north ? start.y + start.height - height : start.y,
|
|
469
|
+
width,
|
|
470
|
+
height
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
function resizeProportionalSnapPoints(start, handle, driving, dx, dy) {
|
|
474
|
+
if (driving === "width") {
|
|
475
|
+
const vertical = WEST_HANDLES.has(handle) ? [start.x + dx] : EAST_HANDLES.has(handle) ? [start.x + start.width + dx] : [];
|
|
476
|
+
return { vertical, horizontal: [] };
|
|
477
|
+
}
|
|
478
|
+
const horizontal = NORTH_HANDLES.has(handle) ? [start.y + dy] : SOUTH_HANDLES.has(handle) ? [start.y + start.height + dy] : [];
|
|
479
|
+
return { vertical: [], horizontal };
|
|
480
|
+
}
|
|
438
481
|
var NO_AXES = { width: false, height: false };
|
|
439
482
|
function resizableAxesAt(doc, path) {
|
|
440
483
|
const layer = resolveLayer(doc, path);
|
|
@@ -1175,6 +1218,27 @@ function defaultNextTextStyle() {
|
|
|
1175
1218
|
lineHeight: base.shape.lineHeight
|
|
1176
1219
|
};
|
|
1177
1220
|
}
|
|
1221
|
+
var RATIO_PRESETS = [
|
|
1222
|
+
{ id: "free", ratio: null },
|
|
1223
|
+
{ id: "1:1", ratio: { w: 1, h: 1 } },
|
|
1224
|
+
{ id: "4:3", ratio: { w: 4, h: 3 } },
|
|
1225
|
+
{ id: "3:4", ratio: { w: 3, h: 4 } },
|
|
1226
|
+
{ id: "16:9", ratio: { w: 16, h: 9 } },
|
|
1227
|
+
{ id: "9:16", ratio: { w: 9, h: 16 } }
|
|
1228
|
+
];
|
|
1229
|
+
function defaultNextLayerRatio() {
|
|
1230
|
+
return "free";
|
|
1231
|
+
}
|
|
1232
|
+
function ratioValue(id) {
|
|
1233
|
+
const entry = RATIO_PRESETS.find((candidate) => candidate.id === id);
|
|
1234
|
+
return entry?.ratio ? { ...entry.ratio } : null;
|
|
1235
|
+
}
|
|
1236
|
+
function pickNextLayerRatio(raw) {
|
|
1237
|
+
return RATIO_PRESETS.some((candidate) => candidate.id === raw) ? raw : "free";
|
|
1238
|
+
}
|
|
1239
|
+
function defaultSizeForRatio(ratio) {
|
|
1240
|
+
return ratio.w >= ratio.h ? { width: 200, height: Math.round(200 * ratio.h / ratio.w) } : { width: Math.round(200 * ratio.w / ratio.h), height: 200 };
|
|
1241
|
+
}
|
|
1178
1242
|
function pickNextTextStylePatch(raw) {
|
|
1179
1243
|
const out = {};
|
|
1180
1244
|
if (raw === null || typeof raw !== "object") return out;
|
|
@@ -1243,7 +1307,8 @@ function createDefaultLayer(type, textDefaults) {
|
|
|
1243
1307
|
}
|
|
1244
1308
|
}
|
|
1245
1309
|
function createTemplateTable(options) {
|
|
1246
|
-
const
|
|
1310
|
+
const defaultTable = createDefaultLayer("TableLayer");
|
|
1311
|
+
const table = options.shape ? { ...defaultTable, shape: { ...defaultTable.shape, width: options.shape.width, height: options.shape.height } } : defaultTable;
|
|
1247
1312
|
const templateBase = createDefaultLayer("TableRowTemplate");
|
|
1248
1313
|
const cellBase = createDefaultLayer("TableCellLayer");
|
|
1249
1314
|
const contentBase = createDefaultLayer("TextLayer");
|
|
@@ -1676,11 +1741,13 @@ var EditorStore = class {
|
|
|
1676
1741
|
drag: null,
|
|
1677
1742
|
resize: null,
|
|
1678
1743
|
armedCreate: null,
|
|
1744
|
+
armedCreateRowsPath: "",
|
|
1679
1745
|
create: null,
|
|
1680
1746
|
editing: null,
|
|
1681
1747
|
renaming: null,
|
|
1682
1748
|
anchorExpanded: false,
|
|
1683
1749
|
nextTextStyle: defaultNextTextStyle(),
|
|
1750
|
+
nextLayerRatio: defaultNextLayerRatio(),
|
|
1684
1751
|
dataSourceSchema: null,
|
|
1685
1752
|
guides: [],
|
|
1686
1753
|
rulersVisible: true,
|
|
@@ -1712,7 +1779,7 @@ var EditorStore = class {
|
|
|
1712
1779
|
}
|
|
1713
1780
|
/** 打开/替换文档:ui 选择/编辑/重命名会话与双向历史一并重置(新文档不继承旧路径/旧事务);
|
|
1714
1781
|
* 参考线/锁定集合/查找会话/建层武装与画拉会话随当次会话清空(对位轴/文档内容防护/
|
|
1715
|
-
*
|
|
1782
|
+
* 查询词/待命层型不跨文档),标尺显隐/锚点折叠/新层样式/建层比例作为偏好保留 */
|
|
1716
1783
|
openDocument(canvas) {
|
|
1717
1784
|
this.docValue = canvas;
|
|
1718
1785
|
this.undoSteps = [];
|
|
@@ -1724,6 +1791,7 @@ var EditorStore = class {
|
|
|
1724
1791
|
drag: null,
|
|
1725
1792
|
resize: null,
|
|
1726
1793
|
armedCreate: null,
|
|
1794
|
+
armedCreateRowsPath: "",
|
|
1727
1795
|
create: null,
|
|
1728
1796
|
editing: null,
|
|
1729
1797
|
renaming: null,
|
|
@@ -1820,10 +1888,15 @@ var EditorStore = class {
|
|
|
1820
1888
|
this.uiValue = { ...this.uiValue, resize: gesture };
|
|
1821
1889
|
this.notify({ scope: "ui", branch: "resize" });
|
|
1822
1890
|
}
|
|
1823
|
-
/**
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1891
|
+
/**
|
|
1892
|
+
* 建层武装置/解除(null,drag-create 工单 01);标量值等短路(重复武装同型
|
|
1893
|
+
* 不通知)。rowsPath(template-table-drag-create 工单 01):模板表风味载荷
|
|
1894
|
+
* 与层型一体写——同层型同载荷才短路(重复武装同型换载荷要更新),解除
|
|
1895
|
+
* (null)一律清载荷。
|
|
1896
|
+
*/
|
|
1897
|
+
setArmedCreate(type, rowsPath = "") {
|
|
1898
|
+
if (this.uiValue.armedCreate === type && this.uiValue.armedCreateRowsPath === rowsPath) return;
|
|
1899
|
+
this.uiValue = { ...this.uiValue, armedCreate: type, armedCreateRowsPath: type === null ? "" : rowsPath };
|
|
1827
1900
|
this.notify({ scope: "ui", branch: "armedCreate" });
|
|
1828
1901
|
}
|
|
1829
1902
|
/** 画拉橡皮筋会话开始/更新/结束(null);会话对象住 ui 分支,落库在 endCreate 单事务 */
|
|
@@ -1856,6 +1929,15 @@ var EditorStore = class {
|
|
|
1856
1929
|
this.uiValue = { ...this.uiValue, nextTextStyle: next };
|
|
1857
1930
|
this.notify({ scope: "ui", branch: "nextTextStyle" });
|
|
1858
1931
|
}
|
|
1932
|
+
/**
|
|
1933
|
+
* 建层比例槽写入(image-ratio-preset 工单 01:比例预设应用 / prefs 恢复的
|
|
1934
|
+
* 统一写入口):值等短路(重复写同档零通知),仅 ui 通知、不进历史。
|
|
1935
|
+
*/
|
|
1936
|
+
setNextLayerRatio(id) {
|
|
1937
|
+
if (this.uiValue.nextLayerRatio === id) return;
|
|
1938
|
+
this.uiValue = { ...this.uiValue, nextLayerRatio: id };
|
|
1939
|
+
this.notify({ scope: "ui", branch: "nextLayerRatio" });
|
|
1940
|
+
}
|
|
1859
1941
|
/**
|
|
1860
1942
|
* 数据源 schema 声明替换(归一产物或降级 null,校验收口在注入缝不在这里):
|
|
1861
1943
|
* 同引用短路(重复注入同一归一产物不惊动订阅方);仅 ui 通知,不参与重绘
|
|
@@ -2740,8 +2822,17 @@ var EditorSession = class {
|
|
|
2740
2822
|
*
|
|
2741
2823
|
* 拖不动写不动的同门守卫:无会话、目标层被结构编辑移除(悬空路径)整体
|
|
2742
2824
|
* 空转(不发事务、不发布命中轴)。
|
|
2825
|
+
*
|
|
2826
|
+
* Shift 等比(image-ratio-preset 工单 01,spec 决策 5):仅 ImageLayer 生效
|
|
2827
|
+
* (其余层型 shift 空转走现状——自适应与尺寸耦合面不被触碰),且须双轴皆在
|
|
2828
|
+
* 可缩放面(格内容等强同步面派生轴写不出去,不进等比模式)、起始盒零维空转
|
|
2829
|
+
* (无比例可参照,回落自由)。参照手势起始盒实际宽高比;驱动轴 = 角柄取位
|
|
2830
|
+
* 移较大轴、边中柄取被拖轴;派生维 = round(驱动维 × 短比)(钳后驱动维跟随,
|
|
2831
|
+
* 最小钳位随动);对缘/对角固定照旧;吸附只在驱动轴移动缘候选(派生轴是算
|
|
2832
|
+
* 术产物不独立吸附,钳先于吸附同门)。等比模式双轴都落声明值(派生轴写算
|
|
2833
|
+
* 术积并清 auto 标志),canonicalize 以驱动轴键收口。
|
|
2743
2834
|
*/
|
|
2744
|
-
resizeTo(sceneX, sceneY) {
|
|
2835
|
+
resizeTo(sceneX, sceneY, shift = false) {
|
|
2745
2836
|
const gesture = this.store.ui.resize;
|
|
2746
2837
|
if (!gesture) return;
|
|
2747
2838
|
const doc = this.store.doc;
|
|
@@ -2751,12 +2842,21 @@ var EditorSession = class {
|
|
|
2751
2842
|
const dy = sceneY - gesture.startScene.y;
|
|
2752
2843
|
const widthActive = handleResizesWidth(gesture.handle);
|
|
2753
2844
|
const heightActive = handleResizesHeight(gesture.handle);
|
|
2845
|
+
const axes = resizableAxesAt(doc, gesture.path);
|
|
2846
|
+
const proportionalDriving = shift && current.type === "ImageLayer" && gesture.startBox.width > 0 && gesture.startBox.height > 0 && axes.width && axes.height ? proportionalDrivingAxis(gesture.handle, dx, dy) : null;
|
|
2754
2847
|
const resolution = resolveSnapPoints(
|
|
2755
|
-
resizeSnapPoints(gesture.startBox, gesture.handle, dx, dy),
|
|
2848
|
+
proportionalDriving !== null ? resizeProportionalSnapPoints(gesture.startBox, gesture.handle, proportionalDriving, dx, dy) : resizeSnapPoints(gesture.startBox, gesture.handle, dx, dy),
|
|
2756
2849
|
this.snapAxesForGesture(gesture.path),
|
|
2757
2850
|
snapThresholdScene(this.store.ui.viewport.zoom)
|
|
2758
2851
|
);
|
|
2759
|
-
const target =
|
|
2852
|
+
const target = proportionalDriving !== null ? resizeBoxProportional(
|
|
2853
|
+
gesture.startBox,
|
|
2854
|
+
gesture.handle,
|
|
2855
|
+
{ w: gesture.startBox.width, h: gesture.startBox.height },
|
|
2856
|
+
proportionalDriving,
|
|
2857
|
+
dx + resolution.dx,
|
|
2858
|
+
dy + resolution.dy
|
|
2859
|
+
) : resizeBox(
|
|
2760
2860
|
gesture.startBox,
|
|
2761
2861
|
gesture.handle,
|
|
2762
2862
|
widthActive ? dx + resolution.dx : dx,
|
|
@@ -2767,11 +2867,11 @@ var EditorSession = class {
|
|
|
2767
2867
|
const layer = resolveLayer(draft, gesture.path);
|
|
2768
2868
|
if (!layer) return;
|
|
2769
2869
|
const shape = layer.shape;
|
|
2770
|
-
if (widthActive) {
|
|
2870
|
+
if (widthActive || proportionalDriving !== null) {
|
|
2771
2871
|
shape.width = target.width;
|
|
2772
2872
|
shape.autoWidth = false;
|
|
2773
2873
|
}
|
|
2774
|
-
if (heightActive) {
|
|
2874
|
+
if (heightActive || proportionalDriving !== null) {
|
|
2775
2875
|
shape.height = target.height;
|
|
2776
2876
|
shape.autoHeight = false;
|
|
2777
2877
|
}
|
|
@@ -2779,7 +2879,7 @@ var EditorSession = class {
|
|
|
2779
2879
|
canonicalizeTableSyncInDraft(
|
|
2780
2880
|
draft,
|
|
2781
2881
|
gesture.path,
|
|
2782
|
-
widthActive ? ["shape", "
|
|
2882
|
+
proportionalDriving === "height" || proportionalDriving === null && !widthActive ? ["shape", "height"] : ["shape", "width"],
|
|
2783
2883
|
this.textPolicies
|
|
2784
2884
|
);
|
|
2785
2885
|
const resolvedWidth = layerWidth(layer, this.textPolicies);
|
|
@@ -2814,22 +2914,42 @@ var EditorSession = class {
|
|
|
2814
2914
|
* 分支待命层型,下一次画布按下即开画拉;建层提交或 Esc 即解除——一次性
|
|
2815
2915
|
* 待命态,无粘性工具(spec 决策 2)。重复武装同型值等短路零通知。
|
|
2816
2916
|
*
|
|
2817
|
-
* preset(text-defaults 工单 01
|
|
2818
|
-
*
|
|
2819
|
-
*
|
|
2820
|
-
*
|
|
2917
|
+
* preset(text-defaults 工单 01 + image-ratio-preset 工单 01):层型相关预设 =
|
|
2918
|
+
* 写槽的快捷方式——命中档位先把档位值并入对应偏好槽再武装;未知 id = 跳过
|
|
2919
|
+
* 写槽、武装照常(评审口径统一:「空转」只指写槽,武装层型本身仍合法)。
|
|
2920
|
+
* - TextLayer + 文本预设档:写新层样式槽(v1 只 fontSize);
|
|
2921
|
+
* - ImageLayer + 比例预设档:写建层比例槽('free' 即显式重置档);QR 恒方
|
|
2922
|
+
* 是层型硬钳、无比例槽(spec 豁免面);
|
|
2923
|
+
* - 交叉类型(文本层带比例档/图片层带文本档)各在对方清单查无此档、天然
|
|
2924
|
+
* 跳写槽。
|
|
2821
2925
|
*/
|
|
2822
2926
|
armLayerCreate(type, preset) {
|
|
2823
2927
|
if (preset !== void 0 && type === "TextLayer") {
|
|
2824
2928
|
const entry = TEXT_PRESETS.find((candidate) => candidate.id === preset);
|
|
2825
2929
|
if (entry) this.store.setNextTextStyle({ fontSize: entry.fontSize });
|
|
2826
2930
|
}
|
|
2931
|
+
if (preset !== void 0 && type === "ImageLayer") {
|
|
2932
|
+
const entry = RATIO_PRESETS.find((candidate) => candidate.id === preset);
|
|
2933
|
+
if (entry) this.store.setNextLayerRatio(entry.id);
|
|
2934
|
+
}
|
|
2827
2935
|
this.store.setArmedCreate(type);
|
|
2828
2936
|
}
|
|
2829
|
-
/**
|
|
2937
|
+
/** 解除建层武装(取消入口):仅清待命态(含模板表风味载荷),零副作用。 */
|
|
2830
2938
|
cancelArmLayerCreate() {
|
|
2831
2939
|
this.store.setArmedCreate(null);
|
|
2832
2940
|
}
|
|
2941
|
+
/**
|
|
2942
|
+
* 武装模板表建层(canvas-web-template-table-drag-create 工单 01,spec 决策 3):
|
|
2943
|
+
* 行路径表单确认即武装——rowsPath 随武装进 ui 瞬态载荷(armedCreate 仍为
|
|
2944
|
+
* 'TableLayer'、不新增 LayerType、不进 graph),下一次画拉或死区点击落模板表。
|
|
2945
|
+
* 空串 no-op(内核防御,表单层已拦);重复武装同 rowsPath 值等短路,换
|
|
2946
|
+
* rowsPath 更新载荷。TableLayer 的 G 武装语义(V1 普通表,armLayerCreate 走
|
|
2947
|
+
* 空载荷)不受染指。
|
|
2948
|
+
*/
|
|
2949
|
+
armTemplateTableCreate(rowsPath) {
|
|
2950
|
+
if (rowsPath === "") return;
|
|
2951
|
+
this.store.setArmedCreate("TableLayer", rowsPath);
|
|
2952
|
+
}
|
|
2833
2953
|
/**
|
|
2834
2954
|
* 开始画拉会话:须已武装(未武装空转防御——画布手势态机的分派前置,此守卫
|
|
2835
2955
|
* 兜其余调用来源),与拖动/缩放会话互斥(手势起点一次性定死,beginDrag/
|
|
@@ -2842,8 +2962,10 @@ var EditorSession = class {
|
|
|
2842
2962
|
if (armed === null || this.store.ui.drag || this.store.ui.resize || this.store.ui.create) return false;
|
|
2843
2963
|
if (!this.store.doc) return false;
|
|
2844
2964
|
const point = { x: sceneX, y: sceneY };
|
|
2965
|
+
const templateRowsPath = armed === "TableLayer" && this.store.ui.armedCreateRowsPath !== "" ? this.store.ui.armedCreateRowsPath : void 0;
|
|
2845
2966
|
this.store.setCreate({
|
|
2846
2967
|
type: armed,
|
|
2968
|
+
templateRowsPath,
|
|
2847
2969
|
startScene: point,
|
|
2848
2970
|
currentScene: point,
|
|
2849
2971
|
rect: { x: sceneX, y: sceneY, width: 0, height: 0 }
|
|
@@ -2852,22 +2974,31 @@ var EditorSession = class {
|
|
|
2852
2974
|
}
|
|
2853
2975
|
/**
|
|
2854
2976
|
* 画拉进行中:橡皮筋求位(spatial/create.createRubberBandRect——两点正规化
|
|
2855
|
-
* + QR 钳 height := width + 左右/上下缘各两点进
|
|
2856
|
-
*
|
|
2857
|
-
* ui.snapAxes 走 GuidesOverlay
|
|
2858
|
-
* (snapAxesForGesture(null)
|
|
2859
|
-
*
|
|
2977
|
+
* + QR 钳 height := width / 比例钳长轴驱动 + 左右/上下缘各两点进
|
|
2978
|
+
* resolveSnapPoints,吸附数学零新增)写回会话 rect 供 overlay 直读(虚线
|
|
2979
|
+
* 矩形 + W×H 气泡),命中轴回写 ui.snapAxes 走 GuidesOverlay 既有通道回显
|
|
2980
|
+
* (dragTo 同门)。吸附排除面为空(snapAxesForGesture(null)——新层尚未入库,
|
|
2981
|
+
* 全部可见根层供轴,copyMode 首移同门)。零文档事务:修正只进会话几何,
|
|
2982
|
+
* 落库在 endCreate。
|
|
2983
|
+
*
|
|
2984
|
+
* 建层比例槽(image-ratio-preset 工单 01,spec 决策 3/4):仅 ImageLayer 按槽
|
|
2985
|
+
* 钳制(QR 层不传 ratio、恒方语义不动,两钳互斥;其余层型无槽);Shift 瞬时
|
|
2986
|
+
* 反转槽态——槽非自由按 Shift = 临时自由、槽自由按 Shift = 临时钳 1:1,按住
|
|
2987
|
+
* 生效松开还原(逐帧求位、不写槽、不改武装态,Figma 惯例的手势瞬时语义)。
|
|
2860
2988
|
*/
|
|
2861
|
-
createTo(sceneX, sceneY) {
|
|
2989
|
+
createTo(sceneX, sceneY, shift = false) {
|
|
2862
2990
|
const gesture = this.store.ui.create;
|
|
2863
2991
|
const doc = this.store.doc;
|
|
2864
2992
|
if (!gesture || !doc) return;
|
|
2993
|
+
const slotRatio = gesture.type === "ImageLayer" ? ratioValue(this.store.ui.nextLayerRatio) : null;
|
|
2994
|
+
const ratio = gesture.type === "ImageLayer" ? shift ? slotRatio === null ? { w: 1, h: 1 } : null : slotRatio : null;
|
|
2865
2995
|
const resolution = createRubberBandRect(
|
|
2866
2996
|
gesture.type,
|
|
2867
2997
|
gesture.startScene,
|
|
2868
2998
|
{ x: sceneX, y: sceneY },
|
|
2869
2999
|
this.snapAxesForGesture(null),
|
|
2870
|
-
snapThresholdScene(this.store.ui.viewport.zoom)
|
|
3000
|
+
snapThresholdScene(this.store.ui.viewport.zoom),
|
|
3001
|
+
ratio
|
|
2871
3002
|
);
|
|
2872
3003
|
this.store.setCreate({
|
|
2873
3004
|
...gesture,
|
|
@@ -2881,12 +3012,19 @@ var EditorSession = class {
|
|
|
2881
3012
|
* + priority)。先清会话再落文档(commitTextEdit 同门——退出路径级联时幂等):
|
|
2882
3013
|
* - 死区外:单事务写入「createDefaultLayer 缺省形态 + position 左上角对准
|
|
2883
3014
|
* 橡皮筋矩形(含吸附修正)+ shape.width/height」并 insertRootLayerInDraft
|
|
2884
|
-
* 置顶(priority min−1、push
|
|
3015
|
+
* 置顶(priority min−1、push 数组尾)——尺寸取手势末态钳制结果(含 Shift
|
|
3016
|
+
* 反转末态,image-ratio-preset 工单 01 spec 决策 4);
|
|
2885
3017
|
* - 死区内(点击兜底,位移按 CREATE_DEAD_ZONE_SCREEN_PX 屏幕 px 随 zoom 折算,
|
|
2886
|
-
* ALT_DRAG_DEAD_ZONE_SCREEN_PX
|
|
3018
|
+
* ALT_DRAG_DEAD_ZONE_SCREEN_PX 同门):左上角对准点击点;尺寸按槽派生缺省
|
|
3019
|
+
* 盒(image-ratio-preset 工单 01:ImageLayer 长边 200 定短边,自由 = 工厂
|
|
3020
|
+
* 200×150 现状;忽略 shift——无位移可供等比约束;其余层型保持工厂缺省)。
|
|
2887
3021
|
* 落库后自动选中新层并解除武装;返回新层路径(绑定层消费:文本层据此自动
|
|
2888
3022
|
* 进文本编辑,spec 决策 6)。无会话/无文档空转返回 null。点击兜底不产生
|
|
2889
3023
|
* 零尺寸层(缺省形态自带可见尺寸)。
|
|
3024
|
+
*
|
|
3025
|
+
* 模板表风味(template-table-drag-create 工单 01):会话载荷 templateRowsPath
|
|
3026
|
+
* 非空即分派 createTemplateTable 工厂(死区外 shape = 手势正规化矩形,死区内
|
|
3027
|
+
* 缺省壳 400×120、左上角对准点击点);模板层不自动进文本编辑(文本层专属)。
|
|
2890
3028
|
*/
|
|
2891
3029
|
endCreate() {
|
|
2892
3030
|
const gesture = this.store.ui.create;
|
|
@@ -2900,9 +3038,23 @@ var EditorSession = class {
|
|
|
2900
3038
|
const deadZoneScene = CREATE_DEAD_ZONE_SCREEN_PX / this.store.ui.viewport.zoom;
|
|
2901
3039
|
const clicked = Math.hypot(dx, dy) <= deadZoneScene;
|
|
2902
3040
|
const index = { v: -1 };
|
|
3041
|
+
const templateRowsPath = gesture.templateRowsPath;
|
|
2903
3042
|
this.store.transact((draft) => {
|
|
2904
|
-
const layer =
|
|
3043
|
+
const layer = templateRowsPath !== void 0 ? createTemplateTable(
|
|
3044
|
+
clicked ? { rowsPath: templateRowsPath } : {
|
|
3045
|
+
rowsPath: templateRowsPath,
|
|
3046
|
+
shape: { width: gesture.rect.width, height: gesture.rect.height }
|
|
3047
|
+
}
|
|
3048
|
+
) : createDefaultLayer(gesture.type, this.store.ui.nextTextStyle);
|
|
2905
3049
|
if (clicked) {
|
|
3050
|
+
if (gesture.type === "ImageLayer") {
|
|
3051
|
+
const ratio = ratioValue(this.store.ui.nextLayerRatio);
|
|
3052
|
+
if (ratio) {
|
|
3053
|
+
const size = defaultSizeForRatio(ratio);
|
|
3054
|
+
layer.shape.width = size.width;
|
|
3055
|
+
layer.shape.height = size.height;
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
2906
3058
|
layer.position = { ...layer.position, x: gesture.startScene.x, y: gesture.startScene.y };
|
|
2907
3059
|
} else {
|
|
2908
3060
|
layer.position = { ...layer.position, x: gesture.rect.x, y: gesture.rect.y };
|
|
@@ -3090,19 +3242,23 @@ var EditorSession = class {
|
|
|
3090
3242
|
return {
|
|
3091
3243
|
rulersVisible: ui.rulersVisible,
|
|
3092
3244
|
anchorExpanded: ui.anchorExpanded,
|
|
3093
|
-
nextTextStyle: ui.nextTextStyle
|
|
3245
|
+
nextTextStyle: ui.nextTextStyle,
|
|
3246
|
+
nextLayerRatio: ui.nextLayerRatio
|
|
3094
3247
|
};
|
|
3095
3248
|
}
|
|
3096
3249
|
/**
|
|
3097
3250
|
* 应用偏好(会话建立时一次性,跨会话恢复 prefs 的写入口):整体替换
|
|
3098
3251
|
* rulersVisible/anchorExpanded,不进历史;值等写入经 store 短路零通知。
|
|
3099
3252
|
* 新层样式(text-defaults 工单 01)走宽容读逐字段合并(pickNextTextStylePatch
|
|
3100
|
-
*
|
|
3253
|
+
* 共享谓词——与捕获同门,缺字/类型不符保留现值);建层比例(image-ratio-preset
|
|
3254
|
+
* 工单 01)走宽容读枚举收口(pickNextLayerRatio——非六档枚举值/旧快照缺字段
|
|
3255
|
+
* 回落 'free')。
|
|
3101
3256
|
*/
|
|
3102
3257
|
applyEditorPrefs(prefs) {
|
|
3103
3258
|
this.store.setRulersVisible(prefs.rulersVisible);
|
|
3104
3259
|
this.store.setAnchorExpanded(prefs.anchorExpanded);
|
|
3105
3260
|
this.store.setNextTextStyle(pickNextTextStylePatch(prefs.nextTextStyle));
|
|
3261
|
+
this.store.setNextLayerRatio(pickNextLayerRatio(prefs.nextLayerRatio));
|
|
3106
3262
|
}
|
|
3107
3263
|
// ---- 对齐画布(layer-align-snap 工单 01):图层盒整体对齐画布几何 ----
|
|
3108
3264
|
/**
|
|
@@ -4058,19 +4214,6 @@ var EditorSession = class {
|
|
|
4058
4214
|
});
|
|
4059
4215
|
if (index.v >= 0) this.store.setSelection([...rowPath, "cells", index.v]);
|
|
4060
4216
|
}
|
|
4061
|
-
/**
|
|
4062
|
-
* 新增模板表(「+」选择器入口,spec §2.1):createTemplateTable 缺省形态 +
|
|
4063
|
-
* 置顶 + 自动选中;一次调用 = 一步历史。rowsPath 必填(解码 rows_path_missing
|
|
4064
|
-
* 硬约束的对齐兜底,表单校验前置),空串 no-op。
|
|
4065
|
-
*/
|
|
4066
|
-
addTemplateTable(rowsPath) {
|
|
4067
|
-
if (!this.store.doc || rowsPath === "") return;
|
|
4068
|
-
const index = { v: -1 };
|
|
4069
|
-
this.store.transact((draft) => {
|
|
4070
|
-
index.v = insertRootLayerInDraft(draft, createTemplateTable({ rowsPath }));
|
|
4071
|
-
});
|
|
4072
|
-
if (index.v >= 0) this.store.setSelection(["layers", index.v]);
|
|
4073
|
-
}
|
|
4074
4217
|
/**
|
|
4075
4218
|
* 模板行加格(spec §3.1):addTemplateCellInDraft 重建路径(末格宽/首格行宽、
|
|
4076
4219
|
* 零高度耦合),自动选中新格;一次调用 = 一步历史。非模板态 no-op。
|
|
@@ -4748,6 +4891,7 @@ export {
|
|
|
4748
4891
|
PAN_TO_BOX_MARGIN_PX,
|
|
4749
4892
|
PASTE_OFFSET_PX,
|
|
4750
4893
|
PHP_TRIM_CHARS,
|
|
4894
|
+
RATIO_PRESETS,
|
|
4751
4895
|
RESIZE_HANDLES,
|
|
4752
4896
|
RESIZE_HANDLE_HIT_PX,
|
|
4753
4897
|
RESIZE_MIN_SIZE_PX,
|
|
@@ -4777,7 +4921,9 @@ export {
|
|
|
4777
4921
|
createDefaultLayer,
|
|
4778
4922
|
createRubberBandRect,
|
|
4779
4923
|
createTemplateTable,
|
|
4924
|
+
defaultNextLayerRatio,
|
|
4780
4925
|
defaultNextTextStyle,
|
|
4926
|
+
defaultSizeForRatio,
|
|
4781
4927
|
deleteLayerInDraft,
|
|
4782
4928
|
enumerateExpressionCandidates,
|
|
4783
4929
|
enumerateRowsPathCandidates,
|
|
@@ -4818,16 +4964,21 @@ export {
|
|
|
4818
4964
|
pathStartsWith,
|
|
4819
4965
|
pathsEqual,
|
|
4820
4966
|
phpTrim2 as phpTrim,
|
|
4967
|
+
pickNextLayerRatio,
|
|
4821
4968
|
pickNextTextStylePatch,
|
|
4822
4969
|
prepareRootPaste,
|
|
4970
|
+
proportionalDrivingAxis,
|
|
4971
|
+
ratioValue,
|
|
4823
4972
|
readSpecField,
|
|
4824
4973
|
remapPathAfterSplice,
|
|
4825
4974
|
replaceHitsInDraft,
|
|
4826
4975
|
resizableAxesAt,
|
|
4827
4976
|
resizeBox,
|
|
4977
|
+
resizeBoxProportional,
|
|
4828
4978
|
resizeHandleAt,
|
|
4829
4979
|
resizeHandlePoint,
|
|
4830
4980
|
resizeHandlesAt,
|
|
4981
|
+
resizeProportionalSnapPoints,
|
|
4831
4982
|
resizeSnapPoints,
|
|
4832
4983
|
resolveLayer,
|
|
4833
4984
|
resolveRowSchema,
|