@hankchen/canvas-editor 0.5.0 → 0.7.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 +227 -48
- package/dist/index.js +208 -55
- 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,26 @@ 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 ratioValue(id) {
|
|
1230
|
+
const entry = RATIO_PRESETS.find((candidate) => candidate.id === id);
|
|
1231
|
+
return entry?.ratio ? { ...entry.ratio } : null;
|
|
1232
|
+
}
|
|
1233
|
+
function findPresetEntry(type, presetId) {
|
|
1234
|
+
if (type === "TextLayer") return TEXT_PRESETS.find((candidate) => candidate.id === presetId);
|
|
1235
|
+
if (type === "ImageLayer") return RATIO_PRESETS.find((candidate) => candidate.id === presetId);
|
|
1236
|
+
return void 0;
|
|
1237
|
+
}
|
|
1238
|
+
function defaultSizeForRatio(ratio) {
|
|
1239
|
+
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 };
|
|
1240
|
+
}
|
|
1178
1241
|
function pickNextTextStylePatch(raw) {
|
|
1179
1242
|
const out = {};
|
|
1180
1243
|
if (raw === null || typeof raw !== "object") return out;
|
|
@@ -1243,7 +1306,8 @@ function createDefaultLayer(type, textDefaults) {
|
|
|
1243
1306
|
}
|
|
1244
1307
|
}
|
|
1245
1308
|
function createTemplateTable(options) {
|
|
1246
|
-
const
|
|
1309
|
+
const defaultTable = createDefaultLayer("TableLayer");
|
|
1310
|
+
const table = options.shape ? { ...defaultTable, shape: { ...defaultTable.shape, width: options.shape.width, height: options.shape.height } } : defaultTable;
|
|
1247
1311
|
const templateBase = createDefaultLayer("TableRowTemplate");
|
|
1248
1312
|
const cellBase = createDefaultLayer("TableCellLayer");
|
|
1249
1313
|
const contentBase = createDefaultLayer("TextLayer");
|
|
@@ -1676,6 +1740,8 @@ var EditorStore = class {
|
|
|
1676
1740
|
drag: null,
|
|
1677
1741
|
resize: null,
|
|
1678
1742
|
armedCreate: null,
|
|
1743
|
+
armedCreateRowsPath: "",
|
|
1744
|
+
armedCreatePreset: "",
|
|
1679
1745
|
create: null,
|
|
1680
1746
|
editing: null,
|
|
1681
1747
|
renaming: null,
|
|
@@ -1724,6 +1790,8 @@ var EditorStore = class {
|
|
|
1724
1790
|
drag: null,
|
|
1725
1791
|
resize: null,
|
|
1726
1792
|
armedCreate: null,
|
|
1793
|
+
armedCreateRowsPath: "",
|
|
1794
|
+
armedCreatePreset: "",
|
|
1727
1795
|
create: null,
|
|
1728
1796
|
editing: null,
|
|
1729
1797
|
renaming: null,
|
|
@@ -1820,10 +1888,23 @@ 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
|
+
* 与层型一体写;preset(canvas-web-preset-submenu 工单 01):预设载荷与层型
|
|
1895
|
+
* 一体写——同层型同载荷才短路(重复武装同型换载荷要更新),解除(null)
|
|
1896
|
+
* 一律清载荷(rowsPath 与 preset 双清)。
|
|
1897
|
+
*/
|
|
1898
|
+
setArmedCreate(type, rowsPath = "", preset = "") {
|
|
1899
|
+
if (this.uiValue.armedCreate === type && this.uiValue.armedCreateRowsPath === rowsPath && this.uiValue.armedCreatePreset === preset) {
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
this.uiValue = {
|
|
1903
|
+
...this.uiValue,
|
|
1904
|
+
armedCreate: type,
|
|
1905
|
+
armedCreateRowsPath: type === null ? "" : rowsPath,
|
|
1906
|
+
armedCreatePreset: type === null ? "" : preset
|
|
1907
|
+
};
|
|
1827
1908
|
this.notify({ scope: "ui", branch: "armedCreate" });
|
|
1828
1909
|
}
|
|
1829
1910
|
/** 画拉橡皮筋会话开始/更新/结束(null);会话对象住 ui 分支,落库在 endCreate 单事务 */
|
|
@@ -1847,8 +1928,9 @@ var EditorStore = class {
|
|
|
1847
1928
|
this.notify({ scope: "ui", branch: "anchorExpanded" });
|
|
1848
1929
|
}
|
|
1849
1930
|
/**
|
|
1850
|
-
* 新层样式槽逐字段合并(text-defaults 工单 01
|
|
1851
|
-
*
|
|
1931
|
+
* 新层样式槽逐字段合并(text-defaults 工单 01:捕获/prefs 恢复的统一写入口;
|
|
1932
|
+
* canvas-web-preset-submenu 工单 01 起预设不再写槽):patch 并入现值,五字段
|
|
1933
|
+
* 值等短路(重复写同值零通知),不进历史。
|
|
1852
1934
|
*/
|
|
1853
1935
|
setNextTextStyle(patch) {
|
|
1854
1936
|
const next = { ...this.uiValue.nextTextStyle, ...patch };
|
|
@@ -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);
|
|
@@ -2812,24 +2912,38 @@ var EditorSession = class {
|
|
|
2812
2912
|
/**
|
|
2813
2913
|
* 武装建层(面板新增项/层型快捷键的统一入口,CONTEXT「武装」词条):置 ui
|
|
2814
2914
|
* 分支待命层型,下一次画布按下即开画拉;建层提交或 Esc 即解除——一次性
|
|
2815
|
-
* 待命态,无粘性工具(spec 决策 2
|
|
2915
|
+
* 待命态,无粘性工具(spec 决策 2)。重复武装同型同载荷值等短路零通知。
|
|
2816
2916
|
*
|
|
2817
|
-
* preset(
|
|
2818
|
-
*
|
|
2819
|
-
*
|
|
2820
|
-
*
|
|
2917
|
+
* preset(canvas-web-preset-submenu 工单 01,ADR 0021):层型相关预设随武装
|
|
2918
|
+
* 载荷一次性携带、**不再写任何偏好槽**——命中档位把档位 id 写进
|
|
2919
|
+
* ui.armedCreatePreset(endCreate 落库一次性消费,取消路零残留);未知 id =
|
|
2920
|
+
* 不落载荷、武装照常(评审口径统一:「空转」只指载荷,武装层型本身仍合法)。
|
|
2921
|
+
* - TextLayer + 文本预设档:载荷命中 TEXT_PRESETS(endCreate 档位字号直接
|
|
2922
|
+
* 生效,不经新层样式槽——普通武装文本按槽的语义不受染指);
|
|
2923
|
+
* - ImageLayer + 比例预设档:载荷命中 RATIO_PRESETS('free' 档载荷行为等价
|
|
2924
|
+
* 自由;QR 恒方是层型硬钳、无比例语义,spec 豁免面);
|
|
2925
|
+
* - 交叉类型(文本层带比例档/图片层带文本档)各在对方清单查无此档、不落载荷。
|
|
2821
2926
|
*/
|
|
2822
2927
|
armLayerCreate(type, preset) {
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
if (entry) this.store.setNextTextStyle({ fontSize: entry.fontSize });
|
|
2826
|
-
}
|
|
2827
|
-
this.store.setArmedCreate(type);
|
|
2928
|
+
const entry = preset === void 0 ? void 0 : findPresetEntry(type, preset);
|
|
2929
|
+
this.store.setArmedCreate(type, "", entry !== void 0 ? entry.id : "");
|
|
2828
2930
|
}
|
|
2829
|
-
/**
|
|
2931
|
+
/** 解除建层武装(取消入口):仅清待命态(含模板表风味与预设载荷),零副作用。 */
|
|
2830
2932
|
cancelArmLayerCreate() {
|
|
2831
2933
|
this.store.setArmedCreate(null);
|
|
2832
2934
|
}
|
|
2935
|
+
/**
|
|
2936
|
+
* 武装模板表建层(canvas-web-template-table-drag-create 工单 01,spec 决策 3):
|
|
2937
|
+
* 行路径表单确认即武装——rowsPath 随武装进 ui 瞬态载荷(armedCreate 仍为
|
|
2938
|
+
* 'TableLayer'、不新增 LayerType、不进 graph),下一次画拉或死区点击落模板表。
|
|
2939
|
+
* 空串 no-op(内核防御,表单层已拦);重复武装同 rowsPath 值等短路,换
|
|
2940
|
+
* rowsPath 更新载荷。TableLayer 的 G 武装语义(V1 普通表,armLayerCreate 走
|
|
2941
|
+
* 空载荷)不受染指。
|
|
2942
|
+
*/
|
|
2943
|
+
armTemplateTableCreate(rowsPath) {
|
|
2944
|
+
if (rowsPath === "") return;
|
|
2945
|
+
this.store.setArmedCreate("TableLayer", rowsPath);
|
|
2946
|
+
}
|
|
2833
2947
|
/**
|
|
2834
2948
|
* 开始画拉会话:须已武装(未武装空转防御——画布手势态机的分派前置,此守卫
|
|
2835
2949
|
* 兜其余调用来源),与拖动/缩放会话互斥(手势起点一次性定死,beginDrag/
|
|
@@ -2842,8 +2956,12 @@ var EditorSession = class {
|
|
|
2842
2956
|
if (armed === null || this.store.ui.drag || this.store.ui.resize || this.store.ui.create) return false;
|
|
2843
2957
|
if (!this.store.doc) return false;
|
|
2844
2958
|
const point = { x: sceneX, y: sceneY };
|
|
2959
|
+
const templateRowsPath = armed === "TableLayer" && this.store.ui.armedCreateRowsPath !== "" ? this.store.ui.armedCreateRowsPath : void 0;
|
|
2960
|
+
const preset = (armed === "TextLayer" || armed === "ImageLayer") && this.store.ui.armedCreatePreset !== "" ? this.store.ui.armedCreatePreset : void 0;
|
|
2845
2961
|
this.store.setCreate({
|
|
2846
2962
|
type: armed,
|
|
2963
|
+
templateRowsPath,
|
|
2964
|
+
preset,
|
|
2847
2965
|
startScene: point,
|
|
2848
2966
|
currentScene: point,
|
|
2849
2967
|
rect: { x: sceneX, y: sceneY, width: 0, height: 0 }
|
|
@@ -2852,22 +2970,33 @@ var EditorSession = class {
|
|
|
2852
2970
|
}
|
|
2853
2971
|
/**
|
|
2854
2972
|
* 画拉进行中:橡皮筋求位(spatial/create.createRubberBandRect——两点正规化
|
|
2855
|
-
* + QR 钳 height := width + 左右/上下缘各两点进
|
|
2856
|
-
*
|
|
2857
|
-
* ui.snapAxes 走 GuidesOverlay
|
|
2858
|
-
* (snapAxesForGesture(null)
|
|
2859
|
-
*
|
|
2973
|
+
* + QR 钳 height := width / 比例钳长轴驱动 + 左右/上下缘各两点进
|
|
2974
|
+
* resolveSnapPoints,吸附数学零新增)写回会话 rect 供 overlay 直读(虚线
|
|
2975
|
+
* 矩形 + W×H 气泡),命中轴回写 ui.snapAxes 走 GuidesOverlay 既有通道回显
|
|
2976
|
+
* (dragTo 同门)。吸附排除面为空(snapAxesForGesture(null)——新层尚未入库,
|
|
2977
|
+
* 全部可见根层供轴,copyMode 首移同门)。零文档事务:修正只进会话几何,
|
|
2978
|
+
* 落库在 endCreate。
|
|
2979
|
+
*
|
|
2980
|
+
* 建层预设档(canvas-web-preset-submenu 工单 01,ADR 0021,取代建层比例槽):
|
|
2981
|
+
* 仅 ImageLayer 按会话载荷档钳制(QR 层不传 ratio、恒方语义不动,两钳互斥;
|
|
2982
|
+
* 其余层型无比例语义);无载荷或 free 档 = 自由钳(普通武装恒自由)。
|
|
2983
|
+
* Shift 瞬时反转源 = 生效档(载荷档或自由):档具名按 Shift = 临时自由、
|
|
2984
|
+
* 生效档自由(含无载荷)按 Shift = 临时钳 1:1,按住生效松开还原(逐帧求位、
|
|
2985
|
+
* 不写载荷不改武装态,Figma 惯例的手势瞬时语义)。
|
|
2860
2986
|
*/
|
|
2861
|
-
createTo(sceneX, sceneY) {
|
|
2987
|
+
createTo(sceneX, sceneY, shift = false) {
|
|
2862
2988
|
const gesture = this.store.ui.create;
|
|
2863
2989
|
const doc = this.store.doc;
|
|
2864
2990
|
if (!gesture || !doc) return;
|
|
2991
|
+
const presetRatio = gesture.type === "ImageLayer" && gesture.preset !== void 0 ? ratioValue(gesture.preset) : null;
|
|
2992
|
+
const ratio = gesture.type === "ImageLayer" ? shift ? presetRatio === null ? { w: 1, h: 1 } : null : presetRatio : null;
|
|
2865
2993
|
const resolution = createRubberBandRect(
|
|
2866
2994
|
gesture.type,
|
|
2867
2995
|
gesture.startScene,
|
|
2868
2996
|
{ x: sceneX, y: sceneY },
|
|
2869
2997
|
this.snapAxesForGesture(null),
|
|
2870
|
-
snapThresholdScene(this.store.ui.viewport.zoom)
|
|
2998
|
+
snapThresholdScene(this.store.ui.viewport.zoom),
|
|
2999
|
+
ratio
|
|
2871
3000
|
);
|
|
2872
3001
|
this.store.setCreate({
|
|
2873
3002
|
...gesture,
|
|
@@ -2881,12 +3010,24 @@ var EditorSession = class {
|
|
|
2881
3010
|
* + priority)。先清会话再落文档(commitTextEdit 同门——退出路径级联时幂等):
|
|
2882
3011
|
* - 死区外:单事务写入「createDefaultLayer 缺省形态 + position 左上角对准
|
|
2883
3012
|
* 橡皮筋矩形(含吸附修正)+ shape.width/height」并 insertRootLayerInDraft
|
|
2884
|
-
* 置顶(priority min−1、push
|
|
3013
|
+
* 置顶(priority min−1、push 数组尾)——尺寸取手势末态钳制结果(含 Shift
|
|
3014
|
+
* 反转末态,钳制源 = 会话预设档,canvas-web-preset-submenu 工单 01);
|
|
2885
3015
|
* - 死区内(点击兜底,位移按 CREATE_DEAD_ZONE_SCREEN_PX 屏幕 px 随 zoom 折算,
|
|
2886
|
-
* ALT_DRAG_DEAD_ZONE_SCREEN_PX
|
|
3016
|
+
* ALT_DRAG_DEAD_ZONE_SCREEN_PX 同门):左上角对准点击点;尺寸按会话预设档
|
|
3017
|
+
* 派生缺省盒(ImageLayer 长边 200 定短边,自由/无载荷 = 工厂 200×150 现状;
|
|
3018
|
+
* 忽略 shift——无位移可供等比约束;其余层型保持工厂缺省)。
|
|
2887
3019
|
* 落库后自动选中新层并解除武装;返回新层路径(绑定层消费:文本层据此自动
|
|
2888
3020
|
* 进文本编辑,spec 决策 6)。无会话/无文档空转返回 null。点击兜底不产生
|
|
2889
3021
|
* 零尺寸层(缺省形态自带可见尺寸)。
|
|
3022
|
+
*
|
|
3023
|
+
* 预设档一次性消费(canvas-web-preset-submenu 工单 01,ADR 0021):文本档
|
|
3024
|
+
* 字号直接生效(不经新层样式槽——字体/行高等槽外字段仍按槽,普通武装语义
|
|
3025
|
+
* 保留);图片档比供死区缺省盒派生。档位随会话捕获,落库后武装与载荷双清
|
|
3026
|
+
* (一次性语义,取消路零残留)。
|
|
3027
|
+
*
|
|
3028
|
+
* 模板表风味(template-table-drag-create 工单 01):会话载荷 templateRowsPath
|
|
3029
|
+
* 非空即分派 createTemplateTable 工厂(死区外 shape = 手势正规化矩形,死区内
|
|
3030
|
+
* 缺省壳 400×120、左上角对准点击点);模板层不自动进文本编辑(文本层专属)。
|
|
2890
3031
|
*/
|
|
2891
3032
|
endCreate() {
|
|
2892
3033
|
const gesture = this.store.ui.create;
|
|
@@ -2900,9 +3041,23 @@ var EditorSession = class {
|
|
|
2900
3041
|
const deadZoneScene = CREATE_DEAD_ZONE_SCREEN_PX / this.store.ui.viewport.zoom;
|
|
2901
3042
|
const clicked = Math.hypot(dx, dy) <= deadZoneScene;
|
|
2902
3043
|
const index = { v: -1 };
|
|
3044
|
+
const templateRowsPath = gesture.templateRowsPath;
|
|
3045
|
+
const presetTextEntry = gesture.type === "TextLayer" && gesture.preset !== void 0 ? findPresetEntry(gesture.type, gesture.preset) : void 0;
|
|
3046
|
+
const textDefaults = presetTextEntry ? { ...this.store.ui.nextTextStyle, fontSize: presetTextEntry.fontSize } : this.store.ui.nextTextStyle;
|
|
3047
|
+
const presetRatio = gesture.type === "ImageLayer" && gesture.preset !== void 0 ? ratioValue(gesture.preset) : null;
|
|
2903
3048
|
this.store.transact((draft) => {
|
|
2904
|
-
const layer =
|
|
3049
|
+
const layer = templateRowsPath !== void 0 ? createTemplateTable(
|
|
3050
|
+
clicked ? { rowsPath: templateRowsPath } : {
|
|
3051
|
+
rowsPath: templateRowsPath,
|
|
3052
|
+
shape: { width: gesture.rect.width, height: gesture.rect.height }
|
|
3053
|
+
}
|
|
3054
|
+
) : createDefaultLayer(gesture.type, textDefaults);
|
|
2905
3055
|
if (clicked) {
|
|
3056
|
+
if (gesture.type === "ImageLayer" && presetRatio) {
|
|
3057
|
+
const size = defaultSizeForRatio(presetRatio);
|
|
3058
|
+
layer.shape.width = size.width;
|
|
3059
|
+
layer.shape.height = size.height;
|
|
3060
|
+
}
|
|
2906
3061
|
layer.position = { ...layer.position, x: gesture.startScene.x, y: gesture.startScene.y };
|
|
2907
3062
|
} else {
|
|
2908
3063
|
layer.position = { ...layer.position, x: gesture.rect.x, y: gesture.rect.y };
|
|
@@ -2917,9 +3072,9 @@ var EditorSession = class {
|
|
|
2917
3072
|
return path;
|
|
2918
3073
|
}
|
|
2919
3074
|
/**
|
|
2920
|
-
* 取消画拉(Esc
|
|
2921
|
-
*
|
|
2922
|
-
*
|
|
3075
|
+
* 取消画拉(Esc 语义):清橡皮筋会话、命中轴回显与武装态(含模板表风味与
|
|
3076
|
+
* 预设载荷)——零文档写入、零历史步(拖拽期纯视觉因此取消零回退成本,spec
|
|
3077
|
+
* 决策 4)。幂等:无会话时仅解除武装(待命态 Esc 同走此口)。
|
|
2923
3078
|
*/
|
|
2924
3079
|
cancelCreate() {
|
|
2925
3080
|
this.store.setCreate(null);
|
|
@@ -3083,7 +3238,9 @@ var EditorSession = class {
|
|
|
3083
3238
|
/**
|
|
3084
3239
|
* 只读偏好(保存 prefs 的读取口):与 store.ui 同源,给宿主一个不用下钻
|
|
3085
3240
|
* store 的口。读 = getEditorPrefs,写 = applyEditorPrefs,对称成对;帧切片
|
|
3086
|
-
* 走 restoreEditorState
|
|
3241
|
+
* 走 restoreEditorState,两不相混。建层比例(nextLayerRatio)已退役不在此
|
|
3242
|
+
* 带出(canvas-web-preset-submenu 工单 01,ADR 0021——预设随武装载荷一次性
|
|
3243
|
+
* 携带,prefs 停写)。
|
|
3087
3244
|
*/
|
|
3088
3245
|
getEditorPrefs() {
|
|
3089
3246
|
const ui = this.store.ui;
|
|
@@ -3097,7 +3254,9 @@ var EditorSession = class {
|
|
|
3097
3254
|
* 应用偏好(会话建立时一次性,跨会话恢复 prefs 的写入口):整体替换
|
|
3098
3255
|
* rulersVisible/anchorExpanded,不进历史;值等写入经 store 短路零通知。
|
|
3099
3256
|
* 新层样式(text-defaults 工单 01)走宽容读逐字段合并(pickNextTextStylePatch
|
|
3100
|
-
*
|
|
3257
|
+
* 共享谓词——与捕获同门,缺字/类型不符保留现值)。旧快照的 nextLayerRatio
|
|
3258
|
+
* 遗留键宽容忽略(canvas-web-preset-submenu 工单 01:槽退役停读,载荷上多余
|
|
3259
|
+
* 的键无害;schemaVersion 加法演进)。
|
|
3101
3260
|
*/
|
|
3102
3261
|
applyEditorPrefs(prefs) {
|
|
3103
3262
|
this.store.setRulersVisible(prefs.rulersVisible);
|
|
@@ -4058,19 +4217,6 @@ var EditorSession = class {
|
|
|
4058
4217
|
});
|
|
4059
4218
|
if (index.v >= 0) this.store.setSelection([...rowPath, "cells", index.v]);
|
|
4060
4219
|
}
|
|
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
4220
|
/**
|
|
4075
4221
|
* 模板行加格(spec §3.1):addTemplateCellInDraft 重建路径(末格宽/首格行宽、
|
|
4076
4222
|
* 零高度耦合),自动选中新格;一次调用 = 一步历史。非模板态 no-op。
|
|
@@ -4748,6 +4894,7 @@ export {
|
|
|
4748
4894
|
PAN_TO_BOX_MARGIN_PX,
|
|
4749
4895
|
PASTE_OFFSET_PX,
|
|
4750
4896
|
PHP_TRIM_CHARS,
|
|
4897
|
+
RATIO_PRESETS,
|
|
4751
4898
|
RESIZE_HANDLES,
|
|
4752
4899
|
RESIZE_HANDLE_HIT_PX,
|
|
4753
4900
|
RESIZE_MIN_SIZE_PX,
|
|
@@ -4778,12 +4925,14 @@ export {
|
|
|
4778
4925
|
createRubberBandRect,
|
|
4779
4926
|
createTemplateTable,
|
|
4780
4927
|
defaultNextTextStyle,
|
|
4928
|
+
defaultSizeForRatio,
|
|
4781
4929
|
deleteLayerInDraft,
|
|
4782
4930
|
enumerateExpressionCandidates,
|
|
4783
4931
|
enumerateRowsPathCandidates,
|
|
4784
4932
|
expressionCallSlotAtEnd,
|
|
4785
4933
|
expressionDiagnostics,
|
|
4786
4934
|
expressionFragmentAtCursor,
|
|
4935
|
+
findPresetEntry,
|
|
4787
4936
|
fitRect,
|
|
4788
4937
|
fitViewport,
|
|
4789
4938
|
growRowToCellInDraft,
|
|
@@ -4820,14 +4969,18 @@ export {
|
|
|
4820
4969
|
phpTrim2 as phpTrim,
|
|
4821
4970
|
pickNextTextStylePatch,
|
|
4822
4971
|
prepareRootPaste,
|
|
4972
|
+
proportionalDrivingAxis,
|
|
4973
|
+
ratioValue,
|
|
4823
4974
|
readSpecField,
|
|
4824
4975
|
remapPathAfterSplice,
|
|
4825
4976
|
replaceHitsInDraft,
|
|
4826
4977
|
resizableAxesAt,
|
|
4827
4978
|
resizeBox,
|
|
4979
|
+
resizeBoxProportional,
|
|
4828
4980
|
resizeHandleAt,
|
|
4829
4981
|
resizeHandlePoint,
|
|
4830
4982
|
resizeHandlesAt,
|
|
4983
|
+
resizeProportionalSnapPoints,
|
|
4831
4984
|
resizeSnapPoints,
|
|
4832
4985
|
resolveLayer,
|
|
4833
4986
|
resolveRowSchema,
|