@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.js CHANGED
@@ -938,7 +938,35 @@ function buildLayerOutline(doc, lockedPaths = []) {
938
938
  }
939
939
  return roots;
940
940
  }
941
- function createDefaultLayer(type) {
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
- return {
989
+ case "TextLayer": {
990
+ const layer = {
963
991
  ...base,
964
992
  type,
965
- shape: { ...base.shape, width: 200, height: 60 },
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 { rulersVisible: ui.rulersVisible, anchorExpanded: ui.anchorExpanded };
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,