@hankchen/canvas-editor 0.2.0 → 0.4.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 +348 -212
- package/dist/index.js +388 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -501,6 +501,7 @@ function normalizeSchemaType(value) {
|
|
|
501
501
|
const primary = value.find((entry) => entry !== "null" && entry !== "");
|
|
502
502
|
return primary;
|
|
503
503
|
}
|
|
504
|
+
var FORMAT_ANNOTATIONS = /* @__PURE__ */ new Set(["date", "date-time"]);
|
|
504
505
|
function annotationsOf(raw) {
|
|
505
506
|
const node = {};
|
|
506
507
|
const rawType = raw["type"];
|
|
@@ -515,6 +516,10 @@ function annotationsOf(raw) {
|
|
|
515
516
|
if (typeof rawDescription === "string") node.description = rawDescription;
|
|
516
517
|
const rawTitle = raw["title"];
|
|
517
518
|
if (typeof rawTitle === "string") node.title = rawTitle;
|
|
519
|
+
const rawFormat = raw["format"];
|
|
520
|
+
if (node.type === "string" && typeof rawFormat === "string" && FORMAT_ANNOTATIONS.has(rawFormat)) {
|
|
521
|
+
node.format = rawFormat;
|
|
522
|
+
}
|
|
518
523
|
return node;
|
|
519
524
|
}
|
|
520
525
|
function pointerEscape(key) {
|
|
@@ -692,15 +697,170 @@ function parseExpressionPath(expr) {
|
|
|
692
697
|
return { ok: true, head: { kind: "bare", name: head }, rest };
|
|
693
698
|
}
|
|
694
699
|
|
|
700
|
+
// src/shared/expressionCall.ts
|
|
701
|
+
var TRIM_CHARS = " \n\r\0\v";
|
|
702
|
+
var WS_CHARS = " \n\r";
|
|
703
|
+
var HEAD_STOP_CHARS = `()',.${TRIM_CHARS}`;
|
|
704
|
+
function phpTrim(value) {
|
|
705
|
+
let start = 0;
|
|
706
|
+
let end = value.length;
|
|
707
|
+
while (start < end && TRIM_CHARS.includes(value[start])) start += 1;
|
|
708
|
+
while (end > start && TRIM_CHARS.includes(value[end - 1])) end -= 1;
|
|
709
|
+
return value.slice(start, end);
|
|
710
|
+
}
|
|
711
|
+
function skipWs(expr, pos) {
|
|
712
|
+
while (pos < expr.length && WS_CHARS.includes(expr[pos])) pos += 1;
|
|
713
|
+
return pos;
|
|
714
|
+
}
|
|
715
|
+
function isCallFragment(expr) {
|
|
716
|
+
let head = 0;
|
|
717
|
+
while (head < expr.length && !HEAD_STOP_CHARS.includes(expr[head])) head += 1;
|
|
718
|
+
if (head === 0) return false;
|
|
719
|
+
let pos = head;
|
|
720
|
+
while (pos < expr.length && TRIM_CHARS.includes(expr[pos])) pos += 1;
|
|
721
|
+
return expr[pos] === "(";
|
|
722
|
+
}
|
|
723
|
+
function parseExpressionCall(expr) {
|
|
724
|
+
let head = 0;
|
|
725
|
+
while (head < expr.length && !HEAD_STOP_CHARS.includes(expr[head])) head += 1;
|
|
726
|
+
if (head === 0) return { ok: false, error: "expression_syntax_error" };
|
|
727
|
+
const name = expr.slice(0, head);
|
|
728
|
+
let pos = skipWs(expr, head);
|
|
729
|
+
if (pos >= expr.length || expr[pos] !== "(") return { ok: false, error: "expression_syntax_error" };
|
|
730
|
+
pos = skipWs(expr, pos + 1);
|
|
731
|
+
const args = [];
|
|
732
|
+
if (pos < expr.length && expr[pos] === ")") {
|
|
733
|
+
pos += 1;
|
|
734
|
+
} else {
|
|
735
|
+
for (; ; ) {
|
|
736
|
+
if (pos >= expr.length) return { ok: false, error: "expression_syntax_error" };
|
|
737
|
+
const arg = parseCallArg(expr, pos);
|
|
738
|
+
if (!arg.ok) return arg;
|
|
739
|
+
args.push(arg.value);
|
|
740
|
+
pos = skipWs(expr, arg.end);
|
|
741
|
+
if (pos >= expr.length) return { ok: false, error: "expression_syntax_error" };
|
|
742
|
+
if (expr[pos] === ",") {
|
|
743
|
+
pos = skipWs(expr, pos + 1);
|
|
744
|
+
if (pos < expr.length && expr[pos] === ")") {
|
|
745
|
+
return { ok: false, error: "expression_syntax_error" };
|
|
746
|
+
}
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
if (expr[pos] === ")") {
|
|
750
|
+
pos += 1;
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
return { ok: false, error: "expression_syntax_error" };
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
if (skipWs(expr, pos) !== expr.length) return { ok: false, error: "expression_syntax_error" };
|
|
757
|
+
if (args[0] !== void 0 && args[0].type !== "path") return { ok: false, error: "expression_syntax_error" };
|
|
758
|
+
for (const arg of args) {
|
|
759
|
+
if (arg.type === "path" && !parseExpressionPath(arg.value).ok) {
|
|
760
|
+
return { ok: false, error: "expression_syntax_error" };
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return { ok: true, name, args };
|
|
764
|
+
}
|
|
765
|
+
function parseCallArg(expr, pos) {
|
|
766
|
+
const len = expr.length;
|
|
767
|
+
const ch = expr[pos];
|
|
768
|
+
if (ch === "'") {
|
|
769
|
+
let cursor = pos + 1;
|
|
770
|
+
let buf = "";
|
|
771
|
+
for (; ; ) {
|
|
772
|
+
if (cursor >= len) return { ok: false, error: "expression_syntax_error" };
|
|
773
|
+
if (expr[cursor] === "'") {
|
|
774
|
+
if (cursor + 1 < len && expr[cursor + 1] === "'") {
|
|
775
|
+
buf += "'";
|
|
776
|
+
cursor += 2;
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
cursor += 1;
|
|
780
|
+
break;
|
|
781
|
+
}
|
|
782
|
+
buf += expr[cursor];
|
|
783
|
+
cursor += 1;
|
|
784
|
+
}
|
|
785
|
+
return { ok: true, value: { type: "string", value: buf }, end: cursor };
|
|
786
|
+
}
|
|
787
|
+
if (ch >= "0" && ch <= "9") {
|
|
788
|
+
const start2 = pos;
|
|
789
|
+
const isDigitAt = (index) => {
|
|
790
|
+
const c = expr[index];
|
|
791
|
+
return c !== void 0 && c >= "0" && c <= "9";
|
|
792
|
+
};
|
|
793
|
+
while (isDigitAt(pos)) pos += 1;
|
|
794
|
+
if (pos < len && expr[pos] === "." && isDigitAt(pos + 1)) {
|
|
795
|
+
pos += 1;
|
|
796
|
+
while (isDigitAt(pos)) pos += 1;
|
|
797
|
+
}
|
|
798
|
+
return { ok: true, value: { type: "number", value: expr.slice(start2, pos) }, end: pos };
|
|
799
|
+
}
|
|
800
|
+
const start = pos;
|
|
801
|
+
while (pos < len && expr[pos] !== "," && expr[pos] !== ")") pos += 1;
|
|
802
|
+
const raw = phpTrim(expr.slice(start, pos));
|
|
803
|
+
if (raw === "") return { ok: false, error: "expression_syntax_error" };
|
|
804
|
+
return { ok: true, value: { type: "path", value: raw }, end: pos };
|
|
805
|
+
}
|
|
806
|
+
function expressionCallSlotAtEnd(expr) {
|
|
807
|
+
let head = 0;
|
|
808
|
+
while (head < expr.length && !HEAD_STOP_CHARS.includes(expr[head])) head += 1;
|
|
809
|
+
if (head === 0) return null;
|
|
810
|
+
let pos = skipWs(expr, head);
|
|
811
|
+
if (pos >= expr.length || expr[pos] !== "(") return null;
|
|
812
|
+
pos = skipWs(expr, pos + 1);
|
|
813
|
+
let index = 0;
|
|
814
|
+
for (; ; ) {
|
|
815
|
+
if (pos >= expr.length) return { name: expr.slice(0, head), index, text: "", stringSlot: false };
|
|
816
|
+
const ch = expr[pos];
|
|
817
|
+
if (ch === "'") {
|
|
818
|
+
let cursor = pos + 1;
|
|
819
|
+
for (; ; ) {
|
|
820
|
+
if (cursor >= expr.length) {
|
|
821
|
+
return { name: expr.slice(0, head), index, text: expr.slice(pos + 1), stringSlot: true };
|
|
822
|
+
}
|
|
823
|
+
if (expr[cursor] === "'") {
|
|
824
|
+
if (cursor + 1 < expr.length && expr[cursor + 1] === "'") {
|
|
825
|
+
cursor += 2;
|
|
826
|
+
continue;
|
|
827
|
+
}
|
|
828
|
+
cursor += 1;
|
|
829
|
+
break;
|
|
830
|
+
}
|
|
831
|
+
cursor += 1;
|
|
832
|
+
}
|
|
833
|
+
pos = cursor;
|
|
834
|
+
} else if (ch !== "," && ch !== ")") {
|
|
835
|
+
const argStart = pos;
|
|
836
|
+
while (pos < expr.length && expr[pos] !== "," && expr[pos] !== ")") pos += 1;
|
|
837
|
+
if (pos >= expr.length) {
|
|
838
|
+
return { name: expr.slice(0, head), index, text: phpTrim(expr.slice(argStart)), stringSlot: false };
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (pos >= expr.length) return { name: expr.slice(0, head), index: index + 1, text: "", stringSlot: false };
|
|
842
|
+
if (expr[pos] === ")") return null;
|
|
843
|
+
index += 1;
|
|
844
|
+
pos = skipWs(expr, pos + 1);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
695
848
|
// src/shared/expressionScan.ts
|
|
696
849
|
var PHP_TRIM_CHARS = " \n\r\0\v";
|
|
697
|
-
function
|
|
850
|
+
function phpTrim2(value) {
|
|
698
851
|
let start = 0;
|
|
699
852
|
let end = value.length;
|
|
700
853
|
while (start < end && PHP_TRIM_CHARS.includes(value[start])) start += 1;
|
|
701
854
|
while (end > start && PHP_TRIM_CHARS.includes(value[end - 1])) end -= 1;
|
|
702
855
|
return value.slice(start, end);
|
|
703
856
|
}
|
|
857
|
+
function fragmentSyntaxError(expr) {
|
|
858
|
+
if (expr === "") return "expression_syntax_error";
|
|
859
|
+
if (isCallFragment(expr)) {
|
|
860
|
+
return parseExpressionCall(expr).ok ? null : "expression_syntax_error";
|
|
861
|
+
}
|
|
862
|
+
return parseExpressionPath(expr).ok ? null : "expression_syntax_error";
|
|
863
|
+
}
|
|
704
864
|
function scanExpressionFragments(template) {
|
|
705
865
|
const parts = [];
|
|
706
866
|
let pos = 0;
|
|
@@ -712,15 +872,15 @@ function scanExpressionFragments(template) {
|
|
|
712
872
|
parts.push({ kind: "literal", start: pos, end: open, text: template.slice(pos, open) });
|
|
713
873
|
}
|
|
714
874
|
const text = template.slice(open);
|
|
715
|
-
parts.push({ kind: "open", start: open, end: template.length, text, expr:
|
|
875
|
+
parts.push({ kind: "open", start: open, end: template.length, text, expr: phpTrim2(text.slice(2)) });
|
|
716
876
|
return { template, parts };
|
|
717
877
|
}
|
|
718
878
|
if (open > pos) {
|
|
719
879
|
parts.push({ kind: "literal", start: pos, end: open, text: template.slice(pos, open) });
|
|
720
880
|
}
|
|
721
881
|
const raw = template.slice(open + 2, close);
|
|
722
|
-
const expr =
|
|
723
|
-
const error =
|
|
882
|
+
const expr = phpTrim2(raw);
|
|
883
|
+
const error = fragmentSyntaxError(expr);
|
|
724
884
|
parts.push({ kind: "fragment", start: open, end: close + 2, raw, expr, error });
|
|
725
885
|
pos = close + 2;
|
|
726
886
|
open = template.indexOf("{{", pos);
|
|
@@ -938,7 +1098,35 @@ function buildLayerOutline(doc, lockedPaths = []) {
|
|
|
938
1098
|
}
|
|
939
1099
|
return roots;
|
|
940
1100
|
}
|
|
941
|
-
|
|
1101
|
+
var TEXT_PRESETS = [
|
|
1102
|
+
{ id: "heading", fontSize: 40 },
|
|
1103
|
+
{ id: "subheading", fontSize: 28 },
|
|
1104
|
+
{ id: "body", fontSize: 16 }
|
|
1105
|
+
];
|
|
1106
|
+
function defaultNextTextStyle() {
|
|
1107
|
+
const base = createDefaultLayer("TextLayer");
|
|
1108
|
+
return {
|
|
1109
|
+
font: base.font,
|
|
1110
|
+
fontSize: base.fontSize,
|
|
1111
|
+
fontColor: base.fontColor,
|
|
1112
|
+
autowrap: base.autowrap,
|
|
1113
|
+
lineHeight: base.shape.lineHeight
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
function pickNextTextStylePatch(raw) {
|
|
1117
|
+
const out = {};
|
|
1118
|
+
if (raw === null || typeof raw !== "object") return out;
|
|
1119
|
+
const source = raw;
|
|
1120
|
+
if (typeof source.font === "string") out.font = source.font;
|
|
1121
|
+
if (typeof source.fontSize === "number" && Number.isFinite(source.fontSize)) out.fontSize = source.fontSize;
|
|
1122
|
+
if (typeof source.fontColor === "string") out.fontColor = source.fontColor;
|
|
1123
|
+
if (typeof source.autowrap === "boolean") out.autowrap = source.autowrap;
|
|
1124
|
+
if (typeof source.lineHeight === "number" && Number.isFinite(source.lineHeight) && source.lineHeight > 0) {
|
|
1125
|
+
out.lineHeight = source.lineHeight;
|
|
1126
|
+
}
|
|
1127
|
+
return out;
|
|
1128
|
+
}
|
|
1129
|
+
function createDefaultLayer(type, textDefaults) {
|
|
942
1130
|
const base = {
|
|
943
1131
|
// name/visible 缺省态(layer-panel-ux 工单 01):未命名 + 可见,wire 键省略
|
|
944
1132
|
name: "",
|
|
@@ -958,19 +1146,26 @@ function createDefaultLayer(type) {
|
|
|
958
1146
|
position: { anchor: "top-left", x: 0, y: 0 }
|
|
959
1147
|
};
|
|
960
1148
|
switch (type) {
|
|
961
|
-
case "TextLayer":
|
|
962
|
-
|
|
1149
|
+
case "TextLayer": {
|
|
1150
|
+
const layer = {
|
|
963
1151
|
...base,
|
|
964
1152
|
type,
|
|
965
|
-
shape: {
|
|
1153
|
+
shape: {
|
|
1154
|
+
...base.shape,
|
|
1155
|
+
width: 200,
|
|
1156
|
+
height: 60,
|
|
1157
|
+
lineHeight: textDefaults?.lineHeight ?? base.shape.lineHeight
|
|
1158
|
+
},
|
|
966
1159
|
text: "\u6587\u672C",
|
|
967
1160
|
expression: null,
|
|
968
|
-
font: "",
|
|
969
|
-
fontSize: 24,
|
|
970
|
-
fontColor: "#111827",
|
|
1161
|
+
font: textDefaults?.font ?? "",
|
|
1162
|
+
fontSize: textDefaults?.fontSize ?? 24,
|
|
1163
|
+
fontColor: textDefaults?.fontColor ?? "#111827",
|
|
971
1164
|
angle: 0,
|
|
972
|
-
autowrap: false
|
|
1165
|
+
autowrap: textDefaults?.autowrap ?? false
|
|
973
1166
|
};
|
|
1167
|
+
return layer;
|
|
1168
|
+
}
|
|
974
1169
|
case "ImageLayer":
|
|
975
1170
|
return { ...base, type, shape: { ...base.shape, width: 200, height: 150 }, src: null, expression: null };
|
|
976
1171
|
case "QrCodeLayer":
|
|
@@ -1051,8 +1246,8 @@ function moveTableRowInDraft(draft, tablePath, fromRow, toRow) {
|
|
|
1051
1246
|
rows.splice(reduced, 0, moved);
|
|
1052
1247
|
return { from: fromRow, to: reduced };
|
|
1053
1248
|
}
|
|
1054
|
-
function addRootLayerInDraft(draft, type) {
|
|
1055
|
-
return insertRootLayerInDraft(draft, createDefaultLayer(type));
|
|
1249
|
+
function addRootLayerInDraft(draft, type, textDefaults) {
|
|
1250
|
+
return insertRootLayerInDraft(draft, createDefaultLayer(type, textDefaults));
|
|
1056
1251
|
}
|
|
1057
1252
|
function insertRootLayerInDraft(draft, layer) {
|
|
1058
1253
|
let min = Number.POSITIVE_INFINITY;
|
|
@@ -1407,6 +1602,9 @@ function resourceStatusesEqual(a, b) {
|
|
|
1407
1602
|
}
|
|
1408
1603
|
return true;
|
|
1409
1604
|
}
|
|
1605
|
+
function nextTextStyleEqual(a, b) {
|
|
1606
|
+
return a.font === b.font && a.fontSize === b.fontSize && a.fontColor === b.fontColor && a.autowrap === b.autowrap && a.lineHeight === b.lineHeight;
|
|
1607
|
+
}
|
|
1410
1608
|
var EditorStore = class {
|
|
1411
1609
|
docValue = null;
|
|
1412
1610
|
uiValue = {
|
|
@@ -1420,6 +1618,7 @@ var EditorStore = class {
|
|
|
1420
1618
|
editing: null,
|
|
1421
1619
|
renaming: null,
|
|
1422
1620
|
anchorExpanded: false,
|
|
1621
|
+
nextTextStyle: defaultNextTextStyle(),
|
|
1423
1622
|
dataSourceSchema: null,
|
|
1424
1623
|
guides: [],
|
|
1425
1624
|
rulersVisible: true,
|
|
@@ -1451,7 +1650,7 @@ var EditorStore = class {
|
|
|
1451
1650
|
}
|
|
1452
1651
|
/** 打开/替换文档:ui 选择/编辑/重命名会话与双向历史一并重置(新文档不继承旧路径/旧事务);
|
|
1453
1652
|
* 参考线/锁定集合/查找会话/建层武装与画拉会话随当次会话清空(对位轴/文档内容防护/
|
|
1454
|
-
*
|
|
1653
|
+
* 查询词/待命层型不跨文档),标尺显隐/锚点折叠/新层样式作为偏好保留 */
|
|
1455
1654
|
openDocument(canvas) {
|
|
1456
1655
|
this.docValue = canvas;
|
|
1457
1656
|
this.undoSteps = [];
|
|
@@ -1585,6 +1784,16 @@ var EditorStore = class {
|
|
|
1585
1784
|
this.uiValue = { ...this.uiValue, anchorExpanded: open };
|
|
1586
1785
|
this.notify({ scope: "ui", branch: "anchorExpanded" });
|
|
1587
1786
|
}
|
|
1787
|
+
/**
|
|
1788
|
+
* 新层样式槽逐字段合并(text-defaults 工单 01:捕获/预设/prefs 恢复的统一
|
|
1789
|
+
* 写入口):patch 并入现值,五字段值等短路(重复写同值零通知),不进历史。
|
|
1790
|
+
*/
|
|
1791
|
+
setNextTextStyle(patch) {
|
|
1792
|
+
const next = { ...this.uiValue.nextTextStyle, ...patch };
|
|
1793
|
+
if (nextTextStyleEqual(this.uiValue.nextTextStyle, next)) return;
|
|
1794
|
+
this.uiValue = { ...this.uiValue, nextTextStyle: next };
|
|
1795
|
+
this.notify({ scope: "ui", branch: "nextTextStyle" });
|
|
1796
|
+
}
|
|
1588
1797
|
/**
|
|
1589
1798
|
* 数据源 schema 声明替换(归一产物或降级 null,校验收口在注入缝不在这里):
|
|
1590
1799
|
* 同引用短路(重复注入同一归一产物不惊动订阅方);仅 ui 通知,不参与重绘
|
|
@@ -1952,6 +2161,12 @@ function clampFindCursor(cursor, count) {
|
|
|
1952
2161
|
if (count <= 0) return 0;
|
|
1953
2162
|
return Math.min(Math.max(cursor, 0), count - 1);
|
|
1954
2163
|
}
|
|
2164
|
+
function captureFieldOfKey(key) {
|
|
2165
|
+
const single = key.length === 1 ? key[0] : null;
|
|
2166
|
+
if (single === "font" || single === "fontSize" || single === "fontColor" || single === "autowrap") return single;
|
|
2167
|
+
if (key.length === 2 && key[0] === "shape" && key[1] === "lineHeight") return "lineHeight";
|
|
2168
|
+
return null;
|
|
2169
|
+
}
|
|
1955
2170
|
function hasValidExpressionFragment(template) {
|
|
1956
2171
|
return scanExpressionFragments(template).parts.some(
|
|
1957
2172
|
(part) => part.kind === "fragment" && part.error === null
|
|
@@ -2536,8 +2751,17 @@ var EditorSession = class {
|
|
|
2536
2751
|
* 武装建层(面板新增项/层型快捷键的统一入口,CONTEXT「武装」词条):置 ui
|
|
2537
2752
|
* 分支待命层型,下一次画布按下即开画拉;建层提交或 Esc 即解除——一次性
|
|
2538
2753
|
* 待命态,无粘性工具(spec 决策 2)。重复武装同型值等短路零通知。
|
|
2754
|
+
*
|
|
2755
|
+
* preset(text-defaults 工单 01,CONTEXT「文本预设」词条):文本预设 = 写
|
|
2756
|
+
* 新层样式槽的快捷方式——命中档位先把档位值并入槽(v1 只 fontSize),再
|
|
2757
|
+
* 武装;仅 TextLayer 生效;未知 id = 跳过写槽、武装照常(评审口径统一:
|
|
2758
|
+
* 「空转」只指写槽,武装层型本身仍合法)。
|
|
2539
2759
|
*/
|
|
2540
|
-
armLayerCreate(type) {
|
|
2760
|
+
armLayerCreate(type, preset) {
|
|
2761
|
+
if (preset !== void 0 && type === "TextLayer") {
|
|
2762
|
+
const entry = TEXT_PRESETS.find((candidate) => candidate.id === preset);
|
|
2763
|
+
if (entry) this.store.setNextTextStyle({ fontSize: entry.fontSize });
|
|
2764
|
+
}
|
|
2541
2765
|
this.store.setArmedCreate(type);
|
|
2542
2766
|
}
|
|
2543
2767
|
/** 解除建层武装(取消入口):仅清待命态,零副作用。 */
|
|
@@ -2615,7 +2839,7 @@ var EditorSession = class {
|
|
|
2615
2839
|
const clicked = Math.hypot(dx, dy) <= deadZoneScene;
|
|
2616
2840
|
const index = { v: -1 };
|
|
2617
2841
|
this.store.transact((draft) => {
|
|
2618
|
-
const layer = createDefaultLayer(gesture.type);
|
|
2842
|
+
const layer = createDefaultLayer(gesture.type, this.store.ui.nextTextStyle);
|
|
2619
2843
|
if (clicked) {
|
|
2620
2844
|
layer.position = { ...layer.position, x: gesture.startScene.x, y: gesture.startScene.y };
|
|
2621
2845
|
} else {
|
|
@@ -2801,15 +3025,22 @@ var EditorSession = class {
|
|
|
2801
3025
|
*/
|
|
2802
3026
|
getEditorPrefs() {
|
|
2803
3027
|
const ui = this.store.ui;
|
|
2804
|
-
return {
|
|
3028
|
+
return {
|
|
3029
|
+
rulersVisible: ui.rulersVisible,
|
|
3030
|
+
anchorExpanded: ui.anchorExpanded,
|
|
3031
|
+
nextTextStyle: ui.nextTextStyle
|
|
3032
|
+
};
|
|
2805
3033
|
}
|
|
2806
3034
|
/**
|
|
2807
3035
|
* 应用偏好(会话建立时一次性,跨会话恢复 prefs 的写入口):整体替换
|
|
2808
3036
|
* rulersVisible/anchorExpanded,不进历史;值等写入经 store 短路零通知。
|
|
3037
|
+
* 新层样式(text-defaults 工单 01)走宽容读逐字段合并(pickNextTextStylePatch
|
|
3038
|
+
* 共享谓词——与捕获同门,缺字/类型不符保留现值)。
|
|
2809
3039
|
*/
|
|
2810
3040
|
applyEditorPrefs(prefs) {
|
|
2811
3041
|
this.store.setRulersVisible(prefs.rulersVisible);
|
|
2812
3042
|
this.store.setAnchorExpanded(prefs.anchorExpanded);
|
|
3043
|
+
this.store.setNextTextStyle(pickNextTextStylePatch(prefs.nextTextStyle));
|
|
2813
3044
|
}
|
|
2814
3045
|
// ---- 对齐画布(layer-align-snap 工单 01):图层盒整体对齐画布几何 ----
|
|
2815
3046
|
/**
|
|
@@ -3037,6 +3268,23 @@ var EditorSession = class {
|
|
|
3037
3268
|
renormalizeAutoHeightInDraft(layer);
|
|
3038
3269
|
canonicalizeTableSyncInDraft(draft, path, key, this.textPolicies);
|
|
3039
3270
|
}, options);
|
|
3271
|
+
this.captureNextTextStyle(path, key, value);
|
|
3272
|
+
}
|
|
3273
|
+
/**
|
|
3274
|
+
* 新层样式捕获(text-defaults 工单 01,CONTEXT「新层样式」词条):属性面板
|
|
3275
|
+
* 对 TextLayer 文本族五字段的权威写顺带更新偏好槽——「用户改过一次,下一个
|
|
3276
|
+
* 新层沿用」。捕获口唯一(updateSpec 漏斗):内容写(updateData)、样式粘贴
|
|
3277
|
+
* (搬运不是偏好)、撤销重做都不经过此口。字段合法性走 editing 域共享谓词
|
|
3278
|
+
* pickNextTextStylePatch(与 prefs 宽容读同门,评审合流不再双写):目标层非
|
|
3279
|
+
* TextLayer、键不命中或值类型不符即空转,符合字段经值等短路入槽。
|
|
3280
|
+
*/
|
|
3281
|
+
captureNextTextStyle(path, key, value) {
|
|
3282
|
+
const layer = this.store.doc ? resolveLayer(this.store.doc, path) : null;
|
|
3283
|
+
if (!layer || layer.type !== "TextLayer") return;
|
|
3284
|
+
const field = captureFieldOfKey(key);
|
|
3285
|
+
if (field === null) return;
|
|
3286
|
+
const patch = pickNextTextStylePatch({ [field]: value });
|
|
3287
|
+
if (patch[field] !== void 0) this.store.setNextTextStyle(patch);
|
|
3040
3288
|
}
|
|
3041
3289
|
/**
|
|
3042
3290
|
* 更新图层数据字段(wire data.value 的领域展开,按 type 分派):
|
|
@@ -3223,7 +3471,7 @@ var EditorSession = class {
|
|
|
3223
3471
|
if (!this.store.doc) return;
|
|
3224
3472
|
const index = { v: -1 };
|
|
3225
3473
|
this.store.transact((draft) => {
|
|
3226
|
-
index.v = addRootLayerInDraft(draft, type);
|
|
3474
|
+
index.v = addRootLayerInDraft(draft, type, this.store.ui.nextTextStyle);
|
|
3227
3475
|
});
|
|
3228
3476
|
if (index.v >= 0) this.store.setSelection(["layers", index.v]);
|
|
3229
3477
|
}
|
|
@@ -3993,6 +4241,22 @@ function classifyWheel(input) {
|
|
|
3993
4241
|
return "pan";
|
|
3994
4242
|
}
|
|
3995
4243
|
|
|
4244
|
+
// src/shared/expressionFunctions.ts
|
|
4245
|
+
var EXPRESSION_FUNCTIONS = [
|
|
4246
|
+
{ name: "date", signature: "date(path[, format])", summary: "\u65E5\u671F\u683C\u5F0F\u5316\uFF08moment \u98CE\u683C token\uFF0C\u9884\u8BBE date/datetime\uFF09" },
|
|
4247
|
+
{ name: "number", signature: "number(path[, n])", summary: "\u5343\u5206\u4F4D\u5206\u7EC4" },
|
|
4248
|
+
{ name: "fixed", signature: "fixed(path[, n])", summary: "\u5B9A\u70B9\u8865\u96F6\uFF08\u7F3A\u7701 2 \u4F4D\uFF09" },
|
|
4249
|
+
{ name: "percent", signature: "percent(path[, n])", summary: "\u767E\u5206\u6BD4\uFF08\u4E58 100\uFF09" }
|
|
4250
|
+
];
|
|
4251
|
+
var NUMBER_FUNCTION_SUGGESTIONS = EXPRESSION_FUNCTIONS.filter(
|
|
4252
|
+
(fn) => fn.name !== "date"
|
|
4253
|
+
);
|
|
4254
|
+
var DATE_FORMAT_SUGGESTIONS = [
|
|
4255
|
+
{ segment: "date", description: "\u9884\u8BBE \u2192 YYYY-MM-DD" },
|
|
4256
|
+
{ segment: "datetime", description: "\u9884\u8BBE \u2192 YYYY-MM-DD HH:mm:ss" },
|
|
4257
|
+
{ segment: "YYYY\u5E74MM\u6708DD\u65E5", description: "\u4E2D\u6587\u6A21\u677F\uFF08\u5199\u5373\u6240\u5F97\uFF09" }
|
|
4258
|
+
];
|
|
4259
|
+
|
|
3996
4260
|
// src/shared/expressionCandidates.ts
|
|
3997
4261
|
function headName(head) {
|
|
3998
4262
|
if (head.kind === "row") return "row";
|
|
@@ -4010,9 +4274,57 @@ function childCandidate(entry, prefix) {
|
|
|
4010
4274
|
const path = prefix === "" ? entry.key : `${prefix}.${entry.key}`;
|
|
4011
4275
|
return { path, segment: entry.key, ...candidateMeta(entry.node) };
|
|
4012
4276
|
}
|
|
4013
|
-
function
|
|
4277
|
+
function callCandidatesFor(entry, path, exprLen) {
|
|
4278
|
+
const extras = [];
|
|
4279
|
+
if (entry.node.format === "date" || entry.node.format === "date-time") {
|
|
4280
|
+
extras.push({
|
|
4281
|
+
path: `date(${path}, '')`,
|
|
4282
|
+
segment: `date(${path}, '')`,
|
|
4283
|
+
cursorBack: 2,
|
|
4284
|
+
replaceLen: exprLen,
|
|
4285
|
+
filterKey: entry.key,
|
|
4286
|
+
description: "\u65E5\u671F\u683C\u5F0F\u5316",
|
|
4287
|
+
type: "\u51FD\u6570"
|
|
4288
|
+
});
|
|
4289
|
+
} else if (entry.node.type === "number") {
|
|
4290
|
+
for (const fn of NUMBER_FUNCTION_SUGGESTIONS) {
|
|
4291
|
+
extras.push({
|
|
4292
|
+
path: `${fn.name}(${path})`,
|
|
4293
|
+
segment: `${fn.name}(${path})`,
|
|
4294
|
+
replaceLen: exprLen,
|
|
4295
|
+
filterKey: entry.key,
|
|
4296
|
+
description: fn.summary,
|
|
4297
|
+
type: "\u51FD\u6570"
|
|
4298
|
+
});
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
return extras;
|
|
4302
|
+
}
|
|
4303
|
+
function childCandidates(entry, prefix, exprLen, withBuckets) {
|
|
4304
|
+
const base = childCandidate(entry, prefix);
|
|
4305
|
+
if (!withBuckets) return [base];
|
|
4306
|
+
return [base, ...callCandidatesFor(entry, base.path, exprLen)];
|
|
4307
|
+
}
|
|
4308
|
+
function functionCandidates() {
|
|
4309
|
+
return EXPRESSION_FUNCTIONS.map((fn) => ({
|
|
4310
|
+
path: fn.signature,
|
|
4311
|
+
segment: `${fn.name}()`,
|
|
4312
|
+
cursorBack: 1,
|
|
4313
|
+
description: fn.summary,
|
|
4314
|
+
type: "\u51FD\u6570"
|
|
4315
|
+
}));
|
|
4316
|
+
}
|
|
4317
|
+
function dateFormatCandidates() {
|
|
4318
|
+
return DATE_FORMAT_SUGGESTIONS.map((suggestion) => ({
|
|
4319
|
+
path: suggestion.segment,
|
|
4320
|
+
segment: suggestion.segment,
|
|
4321
|
+
description: suggestion.description,
|
|
4322
|
+
type: "\u683C\u5F0F"
|
|
4323
|
+
}));
|
|
4324
|
+
}
|
|
4325
|
+
function headCandidates(schema, context, rowSchema, exprLen, withBuckets) {
|
|
4014
4326
|
const view = schemaChildEntries(schema);
|
|
4015
|
-
const candidates = view.entries.
|
|
4327
|
+
const candidates = view.entries.flatMap((entry) => childCandidates(entry, "", exprLen, withBuckets));
|
|
4016
4328
|
candidates.push({ path: "$root", segment: "$root" });
|
|
4017
4329
|
if (context === "row") {
|
|
4018
4330
|
candidates.push(rowSchema ? { path: "row", segment: "row", ...candidateMeta(rowSchema) } : { path: "row", segment: "row" });
|
|
@@ -4020,15 +4332,35 @@ function headCandidates(schema, context, rowSchema) {
|
|
|
4020
4332
|
}
|
|
4021
4333
|
return view.open === true ? { candidates, open: true } : { candidates };
|
|
4022
4334
|
}
|
|
4023
|
-
function
|
|
4335
|
+
function callSlotResult(schema, query) {
|
|
4336
|
+
const slot = expressionCallSlotAtEnd(query.expr);
|
|
4337
|
+
if (slot === null) {
|
|
4338
|
+
return { ok: true, prefix: "", partial: "", candidates: [] };
|
|
4339
|
+
}
|
|
4340
|
+
if (slot.stringSlot && slot.index >= 1 && slot.name === "date") {
|
|
4341
|
+
return { ok: true, prefix: "", partial: slot.text, candidates: dateFormatCandidates() };
|
|
4342
|
+
}
|
|
4343
|
+
if (slot.index === 0 && !isCallFragment(slot.text)) {
|
|
4344
|
+
const sub = enumeratePathCandidates(schema, query, slot.text, false);
|
|
4345
|
+
if (!sub.ok) return { ok: true, prefix: "", partial: "", candidates: [] };
|
|
4346
|
+
return sub;
|
|
4347
|
+
}
|
|
4348
|
+
return { ok: true, prefix: "", partial: "", candidates: [] };
|
|
4349
|
+
}
|
|
4350
|
+
function enumeratePathCandidates(schema, query, expr, allowCallSuggestions) {
|
|
4024
4351
|
if (schema === null) {
|
|
4025
4352
|
return { ok: true, prefix: "", partial: "", candidates: [] };
|
|
4026
4353
|
}
|
|
4027
|
-
const hadTrailingDot =
|
|
4028
|
-
const stripped = hadTrailingDot ?
|
|
4354
|
+
const hadTrailingDot = expr.endsWith(".");
|
|
4355
|
+
const stripped = hadTrailingDot ? expr.slice(0, -1) : expr;
|
|
4029
4356
|
if (stripped === "") {
|
|
4030
|
-
if (
|
|
4031
|
-
return {
|
|
4357
|
+
if (expr === "") {
|
|
4358
|
+
return {
|
|
4359
|
+
ok: true,
|
|
4360
|
+
prefix: "",
|
|
4361
|
+
partial: "",
|
|
4362
|
+
...headCandidates(schema, query.context, query.rowSchema, expr.length, false)
|
|
4363
|
+
};
|
|
4032
4364
|
}
|
|
4033
4365
|
return { ok: false, reason: "expression_syntax_error" };
|
|
4034
4366
|
}
|
|
@@ -4049,8 +4381,17 @@ function enumerateExpressionCandidates(schema, query) {
|
|
|
4049
4381
|
prefixPath = [];
|
|
4050
4382
|
partial = headName(head);
|
|
4051
4383
|
}
|
|
4384
|
+
const withBuckets = allowCallSuggestions && partial !== "";
|
|
4052
4385
|
if (prefixPath.length === 0) {
|
|
4053
|
-
|
|
4386
|
+
const view2 = headCandidates(schema, query.context, query.rowSchema, expr.length, withBuckets);
|
|
4387
|
+
const fns = allowCallSuggestions && expr !== "" ? functionCandidates() : [];
|
|
4388
|
+
return {
|
|
4389
|
+
ok: true,
|
|
4390
|
+
prefix: "",
|
|
4391
|
+
partial,
|
|
4392
|
+
candidates: [...view2.candidates, ...fns],
|
|
4393
|
+
...view2.open === true ? { open: true } : {}
|
|
4394
|
+
};
|
|
4054
4395
|
}
|
|
4055
4396
|
let base;
|
|
4056
4397
|
let segments;
|
|
@@ -4079,10 +4420,19 @@ function enumerateExpressionCandidates(schema, query) {
|
|
|
4079
4420
|
ok: true,
|
|
4080
4421
|
prefix,
|
|
4081
4422
|
partial,
|
|
4082
|
-
candidates: view.entries.
|
|
4423
|
+
candidates: view.entries.flatMap((entry) => childCandidates(entry, prefix, expr.length, withBuckets)),
|
|
4083
4424
|
...view.open === true ? { open: true } : {}
|
|
4084
4425
|
};
|
|
4085
4426
|
}
|
|
4427
|
+
function enumerateExpressionCandidates(schema, query) {
|
|
4428
|
+
if (schema === null) {
|
|
4429
|
+
return { ok: true, prefix: "", partial: "", candidates: [] };
|
|
4430
|
+
}
|
|
4431
|
+
if (isCallFragment(query.expr)) {
|
|
4432
|
+
return callSlotResult(schema, query);
|
|
4433
|
+
}
|
|
4434
|
+
return enumeratePathCandidates(schema, query, query.expr, true);
|
|
4435
|
+
}
|
|
4086
4436
|
|
|
4087
4437
|
// src/shared/rowsPathCandidates.ts
|
|
4088
4438
|
function isArrayNode(node) {
|
|
@@ -4232,15 +4582,18 @@ export {
|
|
|
4232
4582
|
ANCHORS,
|
|
4233
4583
|
ARM_CREATE_LAYER_TYPES,
|
|
4234
4584
|
CREATE_DEAD_ZONE_SCREEN_PX,
|
|
4585
|
+
DATE_FORMAT_SUGGESTIONS,
|
|
4235
4586
|
DEFAULT_EDITOR_SHORTCUTS,
|
|
4236
4587
|
DEFAULT_ZOOM_BOUNDS,
|
|
4237
4588
|
EDITOR_STATE_SCHEMA_VERSION,
|
|
4589
|
+
EXPRESSION_FUNCTIONS,
|
|
4238
4590
|
EditorSession,
|
|
4239
4591
|
EditorStore,
|
|
4240
4592
|
FontCatalog,
|
|
4241
4593
|
HORIZONTAL_ALIGNS,
|
|
4242
4594
|
LAYER_TYPES,
|
|
4243
4595
|
MAX_HISTORY_STEPS,
|
|
4596
|
+
NUMBER_FUNCTION_SUGGESTIONS,
|
|
4244
4597
|
PACKAGE_NAME,
|
|
4245
4598
|
PAN_TO_BOX_MARGIN_PX,
|
|
4246
4599
|
PASTE_OFFSET_PX,
|
|
@@ -4250,6 +4603,7 @@ export {
|
|
|
4250
4603
|
RESIZE_MIN_SIZE_PX,
|
|
4251
4604
|
SNAP_THRESHOLD_SCREEN_PX,
|
|
4252
4605
|
STYLE_FIELD_KEYS_BY_TYPE,
|
|
4606
|
+
TEXT_PRESETS,
|
|
4253
4607
|
UploadHandlerMissingError,
|
|
4254
4608
|
VERTICAL_ALIGNS,
|
|
4255
4609
|
WHEEL_ZOOM_STEP,
|
|
@@ -4273,9 +4627,11 @@ export {
|
|
|
4273
4627
|
createDefaultLayer,
|
|
4274
4628
|
createRubberBandRect,
|
|
4275
4629
|
createTemplateTable,
|
|
4630
|
+
defaultNextTextStyle,
|
|
4276
4631
|
deleteLayerInDraft,
|
|
4277
4632
|
enumerateExpressionCandidates,
|
|
4278
4633
|
enumerateRowsPathCandidates,
|
|
4634
|
+
expressionCallSlotAtEnd,
|
|
4279
4635
|
expressionFragmentAtCursor,
|
|
4280
4636
|
fitRect,
|
|
4281
4637
|
fitViewport,
|
|
@@ -4285,6 +4641,7 @@ export {
|
|
|
4285
4641
|
hitTest,
|
|
4286
4642
|
insertRootLayerAdjacentInDraft,
|
|
4287
4643
|
insertRootLayerInDraft,
|
|
4644
|
+
isCallFragment,
|
|
4288
4645
|
isImageContentUndrawn,
|
|
4289
4646
|
isLayerPath,
|
|
4290
4647
|
isLockedPath,
|
|
@@ -4303,11 +4660,14 @@ export {
|
|
|
4303
4660
|
normalizeExpressionSchemaSource,
|
|
4304
4661
|
panBy,
|
|
4305
4662
|
panToBox,
|
|
4663
|
+
parseExpressionCall,
|
|
4306
4664
|
parseExpressionPath,
|
|
4307
4665
|
parseExpressionSchema,
|
|
4308
4666
|
pastePosition,
|
|
4309
4667
|
pathStartsWith,
|
|
4310
4668
|
pathsEqual,
|
|
4669
|
+
phpTrim2 as phpTrim,
|
|
4670
|
+
pickNextTextStylePatch,
|
|
4311
4671
|
prepareRootPaste,
|
|
4312
4672
|
readSpecField,
|
|
4313
4673
|
remapPathAfterSplice,
|