@hankchen/canvas-editor 0.2.0 → 0.3.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 +220 -150
- package/dist/index.js +106 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -938,7 +938,35 @@ function buildLayerOutline(doc, lockedPaths = []) {
|
|
|
938
938
|
}
|
|
939
939
|
return roots;
|
|
940
940
|
}
|
|
941
|
-
|
|
941
|
+
var TEXT_PRESETS = [
|
|
942
|
+
{ id: "heading", fontSize: 40 },
|
|
943
|
+
{ id: "subheading", fontSize: 28 },
|
|
944
|
+
{ id: "body", fontSize: 16 }
|
|
945
|
+
];
|
|
946
|
+
function defaultNextTextStyle() {
|
|
947
|
+
const base = createDefaultLayer("TextLayer");
|
|
948
|
+
return {
|
|
949
|
+
font: base.font,
|
|
950
|
+
fontSize: base.fontSize,
|
|
951
|
+
fontColor: base.fontColor,
|
|
952
|
+
autowrap: base.autowrap,
|
|
953
|
+
lineHeight: base.shape.lineHeight
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
function pickNextTextStylePatch(raw) {
|
|
957
|
+
const out = {};
|
|
958
|
+
if (raw === null || typeof raw !== "object") return out;
|
|
959
|
+
const source = raw;
|
|
960
|
+
if (typeof source.font === "string") out.font = source.font;
|
|
961
|
+
if (typeof source.fontSize === "number" && Number.isFinite(source.fontSize)) out.fontSize = source.fontSize;
|
|
962
|
+
if (typeof source.fontColor === "string") out.fontColor = source.fontColor;
|
|
963
|
+
if (typeof source.autowrap === "boolean") out.autowrap = source.autowrap;
|
|
964
|
+
if (typeof source.lineHeight === "number" && Number.isFinite(source.lineHeight) && source.lineHeight > 0) {
|
|
965
|
+
out.lineHeight = source.lineHeight;
|
|
966
|
+
}
|
|
967
|
+
return out;
|
|
968
|
+
}
|
|
969
|
+
function createDefaultLayer(type, textDefaults) {
|
|
942
970
|
const base = {
|
|
943
971
|
// name/visible 缺省态(layer-panel-ux 工单 01):未命名 + 可见,wire 键省略
|
|
944
972
|
name: "",
|
|
@@ -958,19 +986,26 @@ function createDefaultLayer(type) {
|
|
|
958
986
|
position: { anchor: "top-left", x: 0, y: 0 }
|
|
959
987
|
};
|
|
960
988
|
switch (type) {
|
|
961
|
-
case "TextLayer":
|
|
962
|
-
|
|
989
|
+
case "TextLayer": {
|
|
990
|
+
const layer = {
|
|
963
991
|
...base,
|
|
964
992
|
type,
|
|
965
|
-
shape: {
|
|
993
|
+
shape: {
|
|
994
|
+
...base.shape,
|
|
995
|
+
width: 200,
|
|
996
|
+
height: 60,
|
|
997
|
+
lineHeight: textDefaults?.lineHeight ?? base.shape.lineHeight
|
|
998
|
+
},
|
|
966
999
|
text: "\u6587\u672C",
|
|
967
1000
|
expression: null,
|
|
968
|
-
font: "",
|
|
969
|
-
fontSize: 24,
|
|
970
|
-
fontColor: "#111827",
|
|
1001
|
+
font: textDefaults?.font ?? "",
|
|
1002
|
+
fontSize: textDefaults?.fontSize ?? 24,
|
|
1003
|
+
fontColor: textDefaults?.fontColor ?? "#111827",
|
|
971
1004
|
angle: 0,
|
|
972
|
-
autowrap: false
|
|
1005
|
+
autowrap: textDefaults?.autowrap ?? false
|
|
973
1006
|
};
|
|
1007
|
+
return layer;
|
|
1008
|
+
}
|
|
974
1009
|
case "ImageLayer":
|
|
975
1010
|
return { ...base, type, shape: { ...base.shape, width: 200, height: 150 }, src: null, expression: null };
|
|
976
1011
|
case "QrCodeLayer":
|
|
@@ -1051,8 +1086,8 @@ function moveTableRowInDraft(draft, tablePath, fromRow, toRow) {
|
|
|
1051
1086
|
rows.splice(reduced, 0, moved);
|
|
1052
1087
|
return { from: fromRow, to: reduced };
|
|
1053
1088
|
}
|
|
1054
|
-
function addRootLayerInDraft(draft, type) {
|
|
1055
|
-
return insertRootLayerInDraft(draft, createDefaultLayer(type));
|
|
1089
|
+
function addRootLayerInDraft(draft, type, textDefaults) {
|
|
1090
|
+
return insertRootLayerInDraft(draft, createDefaultLayer(type, textDefaults));
|
|
1056
1091
|
}
|
|
1057
1092
|
function insertRootLayerInDraft(draft, layer) {
|
|
1058
1093
|
let min = Number.POSITIVE_INFINITY;
|
|
@@ -1407,6 +1442,9 @@ function resourceStatusesEqual(a, b) {
|
|
|
1407
1442
|
}
|
|
1408
1443
|
return true;
|
|
1409
1444
|
}
|
|
1445
|
+
function nextTextStyleEqual(a, b) {
|
|
1446
|
+
return a.font === b.font && a.fontSize === b.fontSize && a.fontColor === b.fontColor && a.autowrap === b.autowrap && a.lineHeight === b.lineHeight;
|
|
1447
|
+
}
|
|
1410
1448
|
var EditorStore = class {
|
|
1411
1449
|
docValue = null;
|
|
1412
1450
|
uiValue = {
|
|
@@ -1420,6 +1458,7 @@ var EditorStore = class {
|
|
|
1420
1458
|
editing: null,
|
|
1421
1459
|
renaming: null,
|
|
1422
1460
|
anchorExpanded: false,
|
|
1461
|
+
nextTextStyle: defaultNextTextStyle(),
|
|
1423
1462
|
dataSourceSchema: null,
|
|
1424
1463
|
guides: [],
|
|
1425
1464
|
rulersVisible: true,
|
|
@@ -1451,7 +1490,7 @@ var EditorStore = class {
|
|
|
1451
1490
|
}
|
|
1452
1491
|
/** 打开/替换文档:ui 选择/编辑/重命名会话与双向历史一并重置(新文档不继承旧路径/旧事务);
|
|
1453
1492
|
* 参考线/锁定集合/查找会话/建层武装与画拉会话随当次会话清空(对位轴/文档内容防护/
|
|
1454
|
-
*
|
|
1493
|
+
* 查询词/待命层型不跨文档),标尺显隐/锚点折叠/新层样式作为偏好保留 */
|
|
1455
1494
|
openDocument(canvas) {
|
|
1456
1495
|
this.docValue = canvas;
|
|
1457
1496
|
this.undoSteps = [];
|
|
@@ -1585,6 +1624,16 @@ var EditorStore = class {
|
|
|
1585
1624
|
this.uiValue = { ...this.uiValue, anchorExpanded: open };
|
|
1586
1625
|
this.notify({ scope: "ui", branch: "anchorExpanded" });
|
|
1587
1626
|
}
|
|
1627
|
+
/**
|
|
1628
|
+
* 新层样式槽逐字段合并(text-defaults 工单 01:捕获/预设/prefs 恢复的统一
|
|
1629
|
+
* 写入口):patch 并入现值,五字段值等短路(重复写同值零通知),不进历史。
|
|
1630
|
+
*/
|
|
1631
|
+
setNextTextStyle(patch) {
|
|
1632
|
+
const next = { ...this.uiValue.nextTextStyle, ...patch };
|
|
1633
|
+
if (nextTextStyleEqual(this.uiValue.nextTextStyle, next)) return;
|
|
1634
|
+
this.uiValue = { ...this.uiValue, nextTextStyle: next };
|
|
1635
|
+
this.notify({ scope: "ui", branch: "nextTextStyle" });
|
|
1636
|
+
}
|
|
1588
1637
|
/**
|
|
1589
1638
|
* 数据源 schema 声明替换(归一产物或降级 null,校验收口在注入缝不在这里):
|
|
1590
1639
|
* 同引用短路(重复注入同一归一产物不惊动订阅方);仅 ui 通知,不参与重绘
|
|
@@ -1952,6 +2001,12 @@ function clampFindCursor(cursor, count) {
|
|
|
1952
2001
|
if (count <= 0) return 0;
|
|
1953
2002
|
return Math.min(Math.max(cursor, 0), count - 1);
|
|
1954
2003
|
}
|
|
2004
|
+
function captureFieldOfKey(key) {
|
|
2005
|
+
const single = key.length === 1 ? key[0] : null;
|
|
2006
|
+
if (single === "font" || single === "fontSize" || single === "fontColor" || single === "autowrap") return single;
|
|
2007
|
+
if (key.length === 2 && key[0] === "shape" && key[1] === "lineHeight") return "lineHeight";
|
|
2008
|
+
return null;
|
|
2009
|
+
}
|
|
1955
2010
|
function hasValidExpressionFragment(template) {
|
|
1956
2011
|
return scanExpressionFragments(template).parts.some(
|
|
1957
2012
|
(part) => part.kind === "fragment" && part.error === null
|
|
@@ -2536,8 +2591,17 @@ var EditorSession = class {
|
|
|
2536
2591
|
* 武装建层(面板新增项/层型快捷键的统一入口,CONTEXT「武装」词条):置 ui
|
|
2537
2592
|
* 分支待命层型,下一次画布按下即开画拉;建层提交或 Esc 即解除——一次性
|
|
2538
2593
|
* 待命态,无粘性工具(spec 决策 2)。重复武装同型值等短路零通知。
|
|
2594
|
+
*
|
|
2595
|
+
* preset(text-defaults 工单 01,CONTEXT「文本预设」词条):文本预设 = 写
|
|
2596
|
+
* 新层样式槽的快捷方式——命中档位先把档位值并入槽(v1 只 fontSize),再
|
|
2597
|
+
* 武装;仅 TextLayer 生效;未知 id = 跳过写槽、武装照常(评审口径统一:
|
|
2598
|
+
* 「空转」只指写槽,武装层型本身仍合法)。
|
|
2539
2599
|
*/
|
|
2540
|
-
armLayerCreate(type) {
|
|
2600
|
+
armLayerCreate(type, preset) {
|
|
2601
|
+
if (preset !== void 0 && type === "TextLayer") {
|
|
2602
|
+
const entry = TEXT_PRESETS.find((candidate) => candidate.id === preset);
|
|
2603
|
+
if (entry) this.store.setNextTextStyle({ fontSize: entry.fontSize });
|
|
2604
|
+
}
|
|
2541
2605
|
this.store.setArmedCreate(type);
|
|
2542
2606
|
}
|
|
2543
2607
|
/** 解除建层武装(取消入口):仅清待命态,零副作用。 */
|
|
@@ -2615,7 +2679,7 @@ var EditorSession = class {
|
|
|
2615
2679
|
const clicked = Math.hypot(dx, dy) <= deadZoneScene;
|
|
2616
2680
|
const index = { v: -1 };
|
|
2617
2681
|
this.store.transact((draft) => {
|
|
2618
|
-
const layer = createDefaultLayer(gesture.type);
|
|
2682
|
+
const layer = createDefaultLayer(gesture.type, this.store.ui.nextTextStyle);
|
|
2619
2683
|
if (clicked) {
|
|
2620
2684
|
layer.position = { ...layer.position, x: gesture.startScene.x, y: gesture.startScene.y };
|
|
2621
2685
|
} else {
|
|
@@ -2801,15 +2865,22 @@ var EditorSession = class {
|
|
|
2801
2865
|
*/
|
|
2802
2866
|
getEditorPrefs() {
|
|
2803
2867
|
const ui = this.store.ui;
|
|
2804
|
-
return {
|
|
2868
|
+
return {
|
|
2869
|
+
rulersVisible: ui.rulersVisible,
|
|
2870
|
+
anchorExpanded: ui.anchorExpanded,
|
|
2871
|
+
nextTextStyle: ui.nextTextStyle
|
|
2872
|
+
};
|
|
2805
2873
|
}
|
|
2806
2874
|
/**
|
|
2807
2875
|
* 应用偏好(会话建立时一次性,跨会话恢复 prefs 的写入口):整体替换
|
|
2808
2876
|
* rulersVisible/anchorExpanded,不进历史;值等写入经 store 短路零通知。
|
|
2877
|
+
* 新层样式(text-defaults 工单 01)走宽容读逐字段合并(pickNextTextStylePatch
|
|
2878
|
+
* 共享谓词——与捕获同门,缺字/类型不符保留现值)。
|
|
2809
2879
|
*/
|
|
2810
2880
|
applyEditorPrefs(prefs) {
|
|
2811
2881
|
this.store.setRulersVisible(prefs.rulersVisible);
|
|
2812
2882
|
this.store.setAnchorExpanded(prefs.anchorExpanded);
|
|
2883
|
+
this.store.setNextTextStyle(pickNextTextStylePatch(prefs.nextTextStyle));
|
|
2813
2884
|
}
|
|
2814
2885
|
// ---- 对齐画布(layer-align-snap 工单 01):图层盒整体对齐画布几何 ----
|
|
2815
2886
|
/**
|
|
@@ -3037,6 +3108,23 @@ var EditorSession = class {
|
|
|
3037
3108
|
renormalizeAutoHeightInDraft(layer);
|
|
3038
3109
|
canonicalizeTableSyncInDraft(draft, path, key, this.textPolicies);
|
|
3039
3110
|
}, options);
|
|
3111
|
+
this.captureNextTextStyle(path, key, value);
|
|
3112
|
+
}
|
|
3113
|
+
/**
|
|
3114
|
+
* 新层样式捕获(text-defaults 工单 01,CONTEXT「新层样式」词条):属性面板
|
|
3115
|
+
* 对 TextLayer 文本族五字段的权威写顺带更新偏好槽——「用户改过一次,下一个
|
|
3116
|
+
* 新层沿用」。捕获口唯一(updateSpec 漏斗):内容写(updateData)、样式粘贴
|
|
3117
|
+
* (搬运不是偏好)、撤销重做都不经过此口。字段合法性走 editing 域共享谓词
|
|
3118
|
+
* pickNextTextStylePatch(与 prefs 宽容读同门,评审合流不再双写):目标层非
|
|
3119
|
+
* TextLayer、键不命中或值类型不符即空转,符合字段经值等短路入槽。
|
|
3120
|
+
*/
|
|
3121
|
+
captureNextTextStyle(path, key, value) {
|
|
3122
|
+
const layer = this.store.doc ? resolveLayer(this.store.doc, path) : null;
|
|
3123
|
+
if (!layer || layer.type !== "TextLayer") return;
|
|
3124
|
+
const field = captureFieldOfKey(key);
|
|
3125
|
+
if (field === null) return;
|
|
3126
|
+
const patch = pickNextTextStylePatch({ [field]: value });
|
|
3127
|
+
if (patch[field] !== void 0) this.store.setNextTextStyle(patch);
|
|
3040
3128
|
}
|
|
3041
3129
|
/**
|
|
3042
3130
|
* 更新图层数据字段(wire data.value 的领域展开,按 type 分派):
|
|
@@ -3223,7 +3311,7 @@ var EditorSession = class {
|
|
|
3223
3311
|
if (!this.store.doc) return;
|
|
3224
3312
|
const index = { v: -1 };
|
|
3225
3313
|
this.store.transact((draft) => {
|
|
3226
|
-
index.v = addRootLayerInDraft(draft, type);
|
|
3314
|
+
index.v = addRootLayerInDraft(draft, type, this.store.ui.nextTextStyle);
|
|
3227
3315
|
});
|
|
3228
3316
|
if (index.v >= 0) this.store.setSelection(["layers", index.v]);
|
|
3229
3317
|
}
|
|
@@ -4250,6 +4338,7 @@ export {
|
|
|
4250
4338
|
RESIZE_MIN_SIZE_PX,
|
|
4251
4339
|
SNAP_THRESHOLD_SCREEN_PX,
|
|
4252
4340
|
STYLE_FIELD_KEYS_BY_TYPE,
|
|
4341
|
+
TEXT_PRESETS,
|
|
4253
4342
|
UploadHandlerMissingError,
|
|
4254
4343
|
VERTICAL_ALIGNS,
|
|
4255
4344
|
WHEEL_ZOOM_STEP,
|
|
@@ -4273,6 +4362,7 @@ export {
|
|
|
4273
4362
|
createDefaultLayer,
|
|
4274
4363
|
createRubberBandRect,
|
|
4275
4364
|
createTemplateTable,
|
|
4365
|
+
defaultNextTextStyle,
|
|
4276
4366
|
deleteLayerInDraft,
|
|
4277
4367
|
enumerateExpressionCandidates,
|
|
4278
4368
|
enumerateRowsPathCandidates,
|
|
@@ -4308,6 +4398,7 @@ export {
|
|
|
4308
4398
|
pastePosition,
|
|
4309
4399
|
pathStartsWith,
|
|
4310
4400
|
pathsEqual,
|
|
4401
|
+
pickNextTextStylePatch,
|
|
4311
4402
|
prepareRootPaste,
|
|
4312
4403
|
readSpecField,
|
|
4313
4404
|
remapPathAfterSplice,
|