@yaoxiu/marketing-dsl 1.1.0 → 1.2.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/README.md +16 -9
- package/dist/index.cjs +102 -22
- package/dist/index.d.cts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +102 -22
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ import { createRuntime } from '@yaoxiu/marketing-dsl';
|
|
|
47
47
|
|
|
48
48
|
const runtime = createRuntime(dsl, {
|
|
49
49
|
user: { shopName: '麦爆了旗舰店', version: '专业版' },
|
|
50
|
-
sources: { listRenewTiers:
|
|
50
|
+
sources: { listRenewTiers: params => api.getTiers(params) },
|
|
51
51
|
handlers: { setNeverRemind: () => localStorage.setItem('never', '1') },
|
|
52
52
|
emit: (event, payload) => console.log(event, payload),
|
|
53
53
|
});
|
|
@@ -56,11 +56,18 @@ const unsubscribe = runtime.subscribe(() => {
|
|
|
56
56
|
render(runtime.getTree()); // 每次通知后重新取树画一遍
|
|
57
57
|
});
|
|
58
58
|
|
|
59
|
+
// 用户信息异步到了、或者要切换编辑态,用 update 就地更新。
|
|
60
|
+
// 不要为此重建 runtime——重建会重新拉数据源、重放曝光埋点,
|
|
61
|
+
// 还会把用户已经切到的 tab 打回默认值。
|
|
62
|
+
runtime.update({ user: { shopName: '麦爆了旗舰店' } });
|
|
63
|
+
|
|
59
64
|
// 卸载时必须调,否则倒计时定时器不会停
|
|
60
65
|
runtime.destroy();
|
|
61
66
|
unsubscribe();
|
|
62
67
|
```
|
|
63
68
|
|
|
69
|
+
`update` 能改 `user` / `handlers` / `editMode`。换 `dsl` 本身要重建 runtime。
|
|
70
|
+
|
|
64
71
|
## 渲染树
|
|
65
72
|
|
|
66
73
|
`getTree()` 返回的是纯数据,所有解释工作都已经做完:插值算完了、条件判断过了、循环展开了、样式转成 CSS 了、坐标换算好了、动作绑好了。
|
|
@@ -97,14 +104,14 @@ unsubscribe();
|
|
|
97
104
|
|
|
98
105
|
这几条是设计红线,不会因为「就差一点点」而放开:
|
|
99
106
|
|
|
100
|
-
| 边界
|
|
101
|
-
|
|
|
102
|
-
| 不执行任意代码
|
|
103
|
-
| 不能沿原型链逃逸
|
|
104
|
-
| 不能通过样式作恶
|
|
105
|
-
| 不能执行 `javascript:` | 链接只允许 `http` / `https` / `mailto` / `tel` 和相对路径
|
|
106
|
-
| 不能打任意接口
|
|
107
|
-
| 不能调任意方法
|
|
107
|
+
| 边界 | 做法 |
|
|
108
|
+
| ---------------------- | -------------------------------------------------------------------- |
|
|
109
|
+
| 不执行任意代码 | 手写解析器,不用 `eval` / `new Function`;不支持函数调用和赋值 |
|
|
110
|
+
| 不能沿原型链逃逸 | `constructor` / `__proto__` / `prototype` 一律读不到,函数值也读不到 |
|
|
111
|
+
| 不能通过样式作恶 | 只有白名单里的属性会输出,`position: fixed` 这类会被丢弃 |
|
|
112
|
+
| 不能执行 `javascript:` | 链接只允许 `http` / `https` / `mailto` / `tel` 和相对路径 |
|
|
113
|
+
| 不能打任意接口 | 数据源用注册名引用,配置里写不了 URL |
|
|
114
|
+
| 不能调任意方法 | `call` 只能调宿主注册在 `handlers` 里的方法 |
|
|
108
115
|
|
|
109
116
|
## 完整 DSL 语法
|
|
110
117
|
|
package/dist/index.cjs
CHANGED
|
@@ -326,9 +326,11 @@ function safeImageUrl(input) {
|
|
|
326
326
|
|
|
327
327
|
// src/actions.ts
|
|
328
328
|
function createDispatcher(options) {
|
|
329
|
-
const { setState, emit, openView, closeTop, closeAll
|
|
329
|
+
const { setState, emit, openView, closeTop, closeAll } = options;
|
|
330
330
|
function dispatch(action, context) {
|
|
331
331
|
if (!action || !action.type) return;
|
|
332
|
+
const handlers = options.handlers || {};
|
|
333
|
+
const editMode = !!options.editMode;
|
|
332
334
|
switch (action.type) {
|
|
333
335
|
case "sequence":
|
|
334
336
|
(action.actions || []).forEach((item) => dispatch(item, context));
|
|
@@ -608,7 +610,13 @@ function resolveTree(input) {
|
|
|
608
610
|
);
|
|
609
611
|
const countdowns = { endTimes: [], precision: "s" };
|
|
610
612
|
const renderLayers = ready ? layers.map(
|
|
611
|
-
(item, index) => resolveLayer(
|
|
613
|
+
(item, index) => resolveLayer(
|
|
614
|
+
item.name,
|
|
615
|
+
item.view,
|
|
616
|
+
index === layers.length - 1,
|
|
617
|
+
input,
|
|
618
|
+
countdowns
|
|
619
|
+
)
|
|
612
620
|
) : [];
|
|
613
621
|
return {
|
|
614
622
|
ready: ready && layers.length > 0,
|
|
@@ -627,7 +635,9 @@ function resolveLayer(name, view, isTop, input, countdowns) {
|
|
|
627
635
|
const rootLayout = stage.layout === "flow" ? "flow" : "absolute";
|
|
628
636
|
const maskClosable = !!stage.maskClosable;
|
|
629
637
|
const onMaskClick = maskClosable ? () => closeTop("mask") : void 0;
|
|
630
|
-
const nodes = (view.nodes || []).map(
|
|
638
|
+
const nodes = (view.nodes || []).map(
|
|
639
|
+
(node, index) => resolveNode(node, `${name}-${index}`, rootLayout, context, input, countdowns)
|
|
640
|
+
).filter((el) => !!el);
|
|
631
641
|
return {
|
|
632
642
|
name,
|
|
633
643
|
type: view.type,
|
|
@@ -690,8 +700,12 @@ function resolveStageStyle(stage, isPopup, context) {
|
|
|
690
700
|
base,
|
|
691
701
|
{
|
|
692
702
|
// width / height 支持数字(px)、'100%' / 'auto' / calc(...) 等相对单位,也支持 {{ }}
|
|
693
|
-
width: toLength(
|
|
694
|
-
|
|
703
|
+
width: toLength(
|
|
704
|
+
interpolate(stage.width === void 0 ? 320 : stage.width, context)
|
|
705
|
+
),
|
|
706
|
+
height: toLength(
|
|
707
|
+
interpolate(stage.height === void 0 ? 400 : stage.height, context)
|
|
708
|
+
)
|
|
695
709
|
},
|
|
696
710
|
// stage.style 也走一遍插值,这样切 tab 时弹窗本身的背景能跟着变
|
|
697
711
|
toCssStyle(interpolateDeep(stage.style, context))
|
|
@@ -746,7 +760,12 @@ function resolveCloseButton(config, layerName, closeTop) {
|
|
|
746
760
|
tag: "img",
|
|
747
761
|
className: "dsl-close-img",
|
|
748
762
|
src: image,
|
|
749
|
-
style: {
|
|
763
|
+
style: {
|
|
764
|
+
width: "100%",
|
|
765
|
+
height: "100%",
|
|
766
|
+
objectFit: "contain",
|
|
767
|
+
display: "block"
|
|
768
|
+
}
|
|
750
769
|
}
|
|
751
770
|
]
|
|
752
771
|
};
|
|
@@ -796,7 +815,14 @@ function resolveNode(node, key, layout, context, input, countdowns) {
|
|
|
796
815
|
className: nodeClass("box", !!onClick),
|
|
797
816
|
style: Object.assign({ position: "relative" }, style),
|
|
798
817
|
onClick,
|
|
799
|
-
children: resolveChildren(
|
|
818
|
+
children: resolveChildren(
|
|
819
|
+
node.children,
|
|
820
|
+
key,
|
|
821
|
+
"absolute",
|
|
822
|
+
context,
|
|
823
|
+
input,
|
|
824
|
+
countdowns
|
|
825
|
+
)
|
|
800
826
|
};
|
|
801
827
|
case "flex":
|
|
802
828
|
return {
|
|
@@ -881,7 +907,14 @@ function resolveRepeat(node, key, context, input, countdowns) {
|
|
|
881
907
|
[node.itemName || "item"]: item,
|
|
882
908
|
[node.indexName || "index"]: index
|
|
883
909
|
});
|
|
884
|
-
return resolveNode(
|
|
910
|
+
return resolveNode(
|
|
911
|
+
node.template,
|
|
912
|
+
`${key}-${index}`,
|
|
913
|
+
"flow",
|
|
914
|
+
itemContext,
|
|
915
|
+
input,
|
|
916
|
+
countdowns
|
|
917
|
+
);
|
|
885
918
|
}).filter((el) => !!el);
|
|
886
919
|
}
|
|
887
920
|
function resolveTabs(node, key, context, input) {
|
|
@@ -954,7 +987,14 @@ function resolveCountdown(node, key, style, context, input, countdowns) {
|
|
|
954
987
|
tag: "div",
|
|
955
988
|
className: "dsl-node dsl-flex dsl-countdown",
|
|
956
989
|
style: Object.assign({ display: "flex" }, style),
|
|
957
|
-
children: resolveChildren(
|
|
990
|
+
children: resolveChildren(
|
|
991
|
+
node.children,
|
|
992
|
+
key,
|
|
993
|
+
"flow",
|
|
994
|
+
childContext,
|
|
995
|
+
input,
|
|
996
|
+
countdowns
|
|
997
|
+
)
|
|
958
998
|
};
|
|
959
999
|
}
|
|
960
1000
|
function toText(value) {
|
|
@@ -965,7 +1005,12 @@ function toText(value) {
|
|
|
965
1005
|
var noopEmit = () => {
|
|
966
1006
|
};
|
|
967
1007
|
function createRuntime(dsl, options = {}) {
|
|
968
|
-
const {
|
|
1008
|
+
const { sources = {}, emit = noopEmit } = options;
|
|
1009
|
+
const live = {
|
|
1010
|
+
user: options.user || {},
|
|
1011
|
+
handlers: options.handlers || {},
|
|
1012
|
+
editMode: !!options.editMode
|
|
1013
|
+
};
|
|
969
1014
|
const normalized = normalizeViews(dsl);
|
|
970
1015
|
let state = Object.assign({}, dsl.state || {});
|
|
971
1016
|
let resolvedData = {};
|
|
@@ -981,7 +1026,10 @@ function createRuntime(dsl, options = {}) {
|
|
|
981
1026
|
};
|
|
982
1027
|
const ticker = createTicker({ onTick: notify });
|
|
983
1028
|
function buildContext() {
|
|
984
|
-
const context = Object.assign({}, resolvedData, {
|
|
1029
|
+
const context = Object.assign({}, resolvedData, {
|
|
1030
|
+
state,
|
|
1031
|
+
user: live.user
|
|
1032
|
+
});
|
|
985
1033
|
const derived = dsl.derived || {};
|
|
986
1034
|
Object.keys(derived).forEach((name) => {
|
|
987
1035
|
const config = derived[name] || {};
|
|
@@ -1042,8 +1090,12 @@ function createRuntime(dsl, options = {}) {
|
|
|
1042
1090
|
// 配置里 close 可以自带 reason(如 'never-remind'),要透传给埋点,不能吞掉
|
|
1043
1091
|
closeTop: (reason) => closeTop(reason),
|
|
1044
1092
|
closeAll: (reason) => closeAll(reason),
|
|
1045
|
-
handlers
|
|
1046
|
-
|
|
1093
|
+
get handlers() {
|
|
1094
|
+
return live.handlers;
|
|
1095
|
+
},
|
|
1096
|
+
get editMode() {
|
|
1097
|
+
return live.editMode;
|
|
1098
|
+
}
|
|
1047
1099
|
});
|
|
1048
1100
|
function fireLifecycle(viewName, hook, extra) {
|
|
1049
1101
|
const view = normalized.views[viewName];
|
|
@@ -1070,7 +1122,7 @@ function createRuntime(dsl, options = {}) {
|
|
|
1070
1122
|
return;
|
|
1071
1123
|
}
|
|
1072
1124
|
pending.push(
|
|
1073
|
-
Promise.resolve(source(ref.params || {}, user)).then((result) => {
|
|
1125
|
+
Promise.resolve(source(ref.params || {}, live.user)).then((result) => {
|
|
1074
1126
|
resolved[key] = result;
|
|
1075
1127
|
})
|
|
1076
1128
|
);
|
|
@@ -1108,6 +1160,12 @@ function createRuntime(dsl, options = {}) {
|
|
|
1108
1160
|
}
|
|
1109
1161
|
return {
|
|
1110
1162
|
getTree,
|
|
1163
|
+
update(patch) {
|
|
1164
|
+
if (patch.user !== void 0) live.user = patch.user;
|
|
1165
|
+
if (patch.handlers !== void 0) live.handlers = patch.handlers;
|
|
1166
|
+
if (patch.editMode !== void 0) live.editMode = patch.editMode;
|
|
1167
|
+
notify();
|
|
1168
|
+
},
|
|
1111
1169
|
subscribe(listener) {
|
|
1112
1170
|
listeners.add(listener);
|
|
1113
1171
|
return () => listeners.delete(listener);
|
|
@@ -1186,7 +1244,10 @@ function validate(dsl) {
|
|
|
1186
1244
|
}
|
|
1187
1245
|
const doc = dsl;
|
|
1188
1246
|
if (doc.version !== DSL_VERSION) {
|
|
1189
|
-
add(
|
|
1247
|
+
add(
|
|
1248
|
+
"version",
|
|
1249
|
+
`version \u5FC5\u987B\u662F ${DSL_VERSION}\uFF0C\u5F53\u524D\u4E3A ${JSON.stringify(doc.version)}`
|
|
1250
|
+
);
|
|
1190
1251
|
}
|
|
1191
1252
|
const dataKeys = validateData(doc.data, add);
|
|
1192
1253
|
const stateKeys = validateState(doc.state, add);
|
|
@@ -1286,7 +1347,10 @@ function validateStage(stage, add, warn, ctx, prefix) {
|
|
|
1286
1347
|
if (stage.onShow !== void 0) validateAction(stage.onShow, at("onShow"), ctx);
|
|
1287
1348
|
if (stage.onClose !== void 0) validateAction(stage.onClose, at("onClose"), ctx);
|
|
1288
1349
|
if (stage.height === "auto" && stage.layout !== "flow") {
|
|
1289
|
-
add(
|
|
1350
|
+
add(
|
|
1351
|
+
"stage.height",
|
|
1352
|
+
"height: 'auto' \u9700\u8981\u540C\u65F6\u8BBE\u7F6E stage.layout: 'flow'\uFF0C\u5426\u5219\u9AD8\u5EA6\u4F1A\u584C\u6210 0"
|
|
1353
|
+
);
|
|
1290
1354
|
}
|
|
1291
1355
|
}
|
|
1292
1356
|
function validateCloseButton(config, add, warn) {
|
|
@@ -1300,7 +1364,10 @@ function validateCloseButton(config, add, warn) {
|
|
|
1300
1364
|
}
|
|
1301
1365
|
if (config.offset !== void 0) {
|
|
1302
1366
|
if (!Array.isArray(config.offset) || config.offset.length !== 2) {
|
|
1303
|
-
add(
|
|
1367
|
+
add(
|
|
1368
|
+
"stage.closeButton.offset",
|
|
1369
|
+
"offset \u5FC5\u987B\u662F [x, y] \u4E24\u9879\u6570\u7EC4\uFF0C\u8D1F\u503C\u4F1A\u628A\u6309\u94AE\u79FB\u5230\u5F39\u7A97\u5916\u9762"
|
|
1370
|
+
);
|
|
1304
1371
|
} else {
|
|
1305
1372
|
config.offset.forEach((value, index) => {
|
|
1306
1373
|
if (!checkLength(value)) {
|
|
@@ -1321,7 +1388,10 @@ function validateCloseButton(config, add, warn) {
|
|
|
1321
1388
|
if (config.style) {
|
|
1322
1389
|
Object.keys(config.style).forEach((key) => {
|
|
1323
1390
|
if (ALLOWED_STYLE_KEYS.indexOf(key) === -1) {
|
|
1324
|
-
warn(
|
|
1391
|
+
warn(
|
|
1392
|
+
`stage.closeButton.style.${key}`,
|
|
1393
|
+
`\u6837\u5F0F\u5C5E\u6027 "${key}" \u4E0D\u5728\u767D\u540D\u5355\u5185\uFF0C\u6E32\u67D3\u65F6\u4F1A\u88AB\u5FFD\u7565`
|
|
1394
|
+
);
|
|
1325
1395
|
}
|
|
1326
1396
|
});
|
|
1327
1397
|
}
|
|
@@ -1392,7 +1462,10 @@ function validateNode(node, path, layout, ctx) {
|
|
|
1392
1462
|
} else {
|
|
1393
1463
|
node.rect.forEach((value, index) => {
|
|
1394
1464
|
if (value !== void 0 && value !== null && !checkLength(value)) {
|
|
1395
|
-
add(
|
|
1465
|
+
add(
|
|
1466
|
+
`${path}.rect[${index}]`,
|
|
1467
|
+
`"${value}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6\uFF0C\u652F\u6301\u6570\u5B57(px) / '100%' / 'auto' \u7B49`
|
|
1468
|
+
);
|
|
1396
1469
|
}
|
|
1397
1470
|
});
|
|
1398
1471
|
}
|
|
@@ -1410,7 +1483,10 @@ function validateNode(node, path, layout, ctx) {
|
|
|
1410
1483
|
);
|
|
1411
1484
|
}
|
|
1412
1485
|
if (key === "aspectRatio" && !checkAspectRatio(node.style[key])) {
|
|
1413
|
-
add(
|
|
1486
|
+
add(
|
|
1487
|
+
`${path}.style.aspectRatio`,
|
|
1488
|
+
"aspectRatio \u8981\u5199\u6210 '750 / 200' \u6216 1.5 \u8FD9\u6837\u7684\u5BBD\u9AD8\u6BD4"
|
|
1489
|
+
);
|
|
1414
1490
|
}
|
|
1415
1491
|
});
|
|
1416
1492
|
}
|
|
@@ -1440,7 +1516,8 @@ function validateNodeByType(node, path, ctx) {
|
|
|
1440
1516
|
if (typeof node.bind !== "string") {
|
|
1441
1517
|
add(`${path}.bind`, "tabs \u5FC5\u987B\u63D0\u4F9B bind\uFF08data \u4E2D\u7684\u6570\u7EC4\u5B57\u6BB5\u540D\uFF09");
|
|
1442
1518
|
}
|
|
1443
|
-
if (typeof node.stateKey !== "string")
|
|
1519
|
+
if (typeof node.stateKey !== "string")
|
|
1520
|
+
add(`${path}.stateKey`, "tabs \u5FC5\u987B\u63D0\u4F9B stateKey");
|
|
1444
1521
|
if (node.labelField !== void 0 && typeof node.labelField !== "string") {
|
|
1445
1522
|
add(`${path}.labelField`, "labelField \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
1446
1523
|
}
|
|
@@ -1506,7 +1583,10 @@ function validateAction(action, path, ctx) {
|
|
|
1506
1583
|
add(`${path}.view`, `views \u91CC\u4E0D\u5B58\u5728\u89C6\u56FE "${action.view}"`);
|
|
1507
1584
|
}
|
|
1508
1585
|
if (action.mode !== void 0 && ["stack", "replace"].indexOf(action.mode) === -1) {
|
|
1509
|
-
add(
|
|
1586
|
+
add(
|
|
1587
|
+
`${path}.mode`,
|
|
1588
|
+
"mode \u53EA\u80FD\u662F 'stack'\uFF08\u9ED8\u8BA4\uFF0C\u53E0\u4E00\u5C42\uFF09\u6216 'replace'\uFF08\u6362\u6389\u5F53\u524D\u5C42\uFF09"
|
|
1589
|
+
);
|
|
1510
1590
|
}
|
|
1511
1591
|
}
|
|
1512
1592
|
if (action.type === "sequence") {
|
package/dist/index.d.cts
CHANGED
|
@@ -293,6 +293,16 @@ interface RuntimeOptions {
|
|
|
293
293
|
interface DslRuntime {
|
|
294
294
|
/** 当前渲染树。每次 subscribe 回调后重新取 */
|
|
295
295
|
getTree: () => RenderTree;
|
|
296
|
+
/**
|
|
297
|
+
* 更新宿主传进来的东西。
|
|
298
|
+
*
|
|
299
|
+
* user 常常是异步到的(弹窗先挂载、用户信息后到),
|
|
300
|
+
* 不给更新入口的话文案就永远停在初始值。
|
|
301
|
+
* editMode / handlers 同理,宿主可能中途换。
|
|
302
|
+
*
|
|
303
|
+
* 不会重新拉数据源,也不会重放 onShow 曝光埋点——那些是一次性的。
|
|
304
|
+
*/
|
|
305
|
+
update: (patch: Pick<RuntimeOptions, 'user' | 'handlers' | 'editMode'>) => void;
|
|
296
306
|
/** 状态变化时回调,返回取消订阅的函数 */
|
|
297
307
|
subscribe: (listener: () => void) => () => void;
|
|
298
308
|
/** 供调试台等外部读取,正常渲染不需要 */
|
package/dist/index.d.ts
CHANGED
|
@@ -293,6 +293,16 @@ interface RuntimeOptions {
|
|
|
293
293
|
interface DslRuntime {
|
|
294
294
|
/** 当前渲染树。每次 subscribe 回调后重新取 */
|
|
295
295
|
getTree: () => RenderTree;
|
|
296
|
+
/**
|
|
297
|
+
* 更新宿主传进来的东西。
|
|
298
|
+
*
|
|
299
|
+
* user 常常是异步到的(弹窗先挂载、用户信息后到),
|
|
300
|
+
* 不给更新入口的话文案就永远停在初始值。
|
|
301
|
+
* editMode / handlers 同理,宿主可能中途换。
|
|
302
|
+
*
|
|
303
|
+
* 不会重新拉数据源,也不会重放 onShow 曝光埋点——那些是一次性的。
|
|
304
|
+
*/
|
|
305
|
+
update: (patch: Pick<RuntimeOptions, 'user' | 'handlers' | 'editMode'>) => void;
|
|
296
306
|
/** 状态变化时回调,返回取消订阅的函数 */
|
|
297
307
|
subscribe: (listener: () => void) => () => void;
|
|
298
308
|
/** 供调试台等外部读取,正常渲染不需要 */
|
package/dist/index.js
CHANGED
|
@@ -324,9 +324,11 @@ function safeImageUrl(input) {
|
|
|
324
324
|
|
|
325
325
|
// src/actions.ts
|
|
326
326
|
function createDispatcher(options) {
|
|
327
|
-
const { setState, emit, openView, closeTop, closeAll
|
|
327
|
+
const { setState, emit, openView, closeTop, closeAll } = options;
|
|
328
328
|
function dispatch(action, context) {
|
|
329
329
|
if (!action || !action.type) return;
|
|
330
|
+
const handlers = options.handlers || {};
|
|
331
|
+
const editMode = !!options.editMode;
|
|
330
332
|
switch (action.type) {
|
|
331
333
|
case "sequence":
|
|
332
334
|
(action.actions || []).forEach((item) => dispatch(item, context));
|
|
@@ -606,7 +608,13 @@ function resolveTree(input) {
|
|
|
606
608
|
);
|
|
607
609
|
const countdowns = { endTimes: [], precision: "s" };
|
|
608
610
|
const renderLayers = ready ? layers.map(
|
|
609
|
-
(item, index) => resolveLayer(
|
|
611
|
+
(item, index) => resolveLayer(
|
|
612
|
+
item.name,
|
|
613
|
+
item.view,
|
|
614
|
+
index === layers.length - 1,
|
|
615
|
+
input,
|
|
616
|
+
countdowns
|
|
617
|
+
)
|
|
610
618
|
) : [];
|
|
611
619
|
return {
|
|
612
620
|
ready: ready && layers.length > 0,
|
|
@@ -625,7 +633,9 @@ function resolveLayer(name, view, isTop, input, countdowns) {
|
|
|
625
633
|
const rootLayout = stage.layout === "flow" ? "flow" : "absolute";
|
|
626
634
|
const maskClosable = !!stage.maskClosable;
|
|
627
635
|
const onMaskClick = maskClosable ? () => closeTop("mask") : void 0;
|
|
628
|
-
const nodes = (view.nodes || []).map(
|
|
636
|
+
const nodes = (view.nodes || []).map(
|
|
637
|
+
(node, index) => resolveNode(node, `${name}-${index}`, rootLayout, context, input, countdowns)
|
|
638
|
+
).filter((el) => !!el);
|
|
629
639
|
return {
|
|
630
640
|
name,
|
|
631
641
|
type: view.type,
|
|
@@ -688,8 +698,12 @@ function resolveStageStyle(stage, isPopup, context) {
|
|
|
688
698
|
base,
|
|
689
699
|
{
|
|
690
700
|
// width / height 支持数字(px)、'100%' / 'auto' / calc(...) 等相对单位,也支持 {{ }}
|
|
691
|
-
width: toLength(
|
|
692
|
-
|
|
701
|
+
width: toLength(
|
|
702
|
+
interpolate(stage.width === void 0 ? 320 : stage.width, context)
|
|
703
|
+
),
|
|
704
|
+
height: toLength(
|
|
705
|
+
interpolate(stage.height === void 0 ? 400 : stage.height, context)
|
|
706
|
+
)
|
|
693
707
|
},
|
|
694
708
|
// stage.style 也走一遍插值,这样切 tab 时弹窗本身的背景能跟着变
|
|
695
709
|
toCssStyle(interpolateDeep(stage.style, context))
|
|
@@ -744,7 +758,12 @@ function resolveCloseButton(config, layerName, closeTop) {
|
|
|
744
758
|
tag: "img",
|
|
745
759
|
className: "dsl-close-img",
|
|
746
760
|
src: image,
|
|
747
|
-
style: {
|
|
761
|
+
style: {
|
|
762
|
+
width: "100%",
|
|
763
|
+
height: "100%",
|
|
764
|
+
objectFit: "contain",
|
|
765
|
+
display: "block"
|
|
766
|
+
}
|
|
748
767
|
}
|
|
749
768
|
]
|
|
750
769
|
};
|
|
@@ -794,7 +813,14 @@ function resolveNode(node, key, layout, context, input, countdowns) {
|
|
|
794
813
|
className: nodeClass("box", !!onClick),
|
|
795
814
|
style: Object.assign({ position: "relative" }, style),
|
|
796
815
|
onClick,
|
|
797
|
-
children: resolveChildren(
|
|
816
|
+
children: resolveChildren(
|
|
817
|
+
node.children,
|
|
818
|
+
key,
|
|
819
|
+
"absolute",
|
|
820
|
+
context,
|
|
821
|
+
input,
|
|
822
|
+
countdowns
|
|
823
|
+
)
|
|
798
824
|
};
|
|
799
825
|
case "flex":
|
|
800
826
|
return {
|
|
@@ -879,7 +905,14 @@ function resolveRepeat(node, key, context, input, countdowns) {
|
|
|
879
905
|
[node.itemName || "item"]: item,
|
|
880
906
|
[node.indexName || "index"]: index
|
|
881
907
|
});
|
|
882
|
-
return resolveNode(
|
|
908
|
+
return resolveNode(
|
|
909
|
+
node.template,
|
|
910
|
+
`${key}-${index}`,
|
|
911
|
+
"flow",
|
|
912
|
+
itemContext,
|
|
913
|
+
input,
|
|
914
|
+
countdowns
|
|
915
|
+
);
|
|
883
916
|
}).filter((el) => !!el);
|
|
884
917
|
}
|
|
885
918
|
function resolveTabs(node, key, context, input) {
|
|
@@ -952,7 +985,14 @@ function resolveCountdown(node, key, style, context, input, countdowns) {
|
|
|
952
985
|
tag: "div",
|
|
953
986
|
className: "dsl-node dsl-flex dsl-countdown",
|
|
954
987
|
style: Object.assign({ display: "flex" }, style),
|
|
955
|
-
children: resolveChildren(
|
|
988
|
+
children: resolveChildren(
|
|
989
|
+
node.children,
|
|
990
|
+
key,
|
|
991
|
+
"flow",
|
|
992
|
+
childContext,
|
|
993
|
+
input,
|
|
994
|
+
countdowns
|
|
995
|
+
)
|
|
956
996
|
};
|
|
957
997
|
}
|
|
958
998
|
function toText(value) {
|
|
@@ -963,7 +1003,12 @@ function toText(value) {
|
|
|
963
1003
|
var noopEmit = () => {
|
|
964
1004
|
};
|
|
965
1005
|
function createRuntime(dsl, options = {}) {
|
|
966
|
-
const {
|
|
1006
|
+
const { sources = {}, emit = noopEmit } = options;
|
|
1007
|
+
const live = {
|
|
1008
|
+
user: options.user || {},
|
|
1009
|
+
handlers: options.handlers || {},
|
|
1010
|
+
editMode: !!options.editMode
|
|
1011
|
+
};
|
|
967
1012
|
const normalized = normalizeViews(dsl);
|
|
968
1013
|
let state = Object.assign({}, dsl.state || {});
|
|
969
1014
|
let resolvedData = {};
|
|
@@ -979,7 +1024,10 @@ function createRuntime(dsl, options = {}) {
|
|
|
979
1024
|
};
|
|
980
1025
|
const ticker = createTicker({ onTick: notify });
|
|
981
1026
|
function buildContext() {
|
|
982
|
-
const context = Object.assign({}, resolvedData, {
|
|
1027
|
+
const context = Object.assign({}, resolvedData, {
|
|
1028
|
+
state,
|
|
1029
|
+
user: live.user
|
|
1030
|
+
});
|
|
983
1031
|
const derived = dsl.derived || {};
|
|
984
1032
|
Object.keys(derived).forEach((name) => {
|
|
985
1033
|
const config = derived[name] || {};
|
|
@@ -1040,8 +1088,12 @@ function createRuntime(dsl, options = {}) {
|
|
|
1040
1088
|
// 配置里 close 可以自带 reason(如 'never-remind'),要透传给埋点,不能吞掉
|
|
1041
1089
|
closeTop: (reason) => closeTop(reason),
|
|
1042
1090
|
closeAll: (reason) => closeAll(reason),
|
|
1043
|
-
handlers
|
|
1044
|
-
|
|
1091
|
+
get handlers() {
|
|
1092
|
+
return live.handlers;
|
|
1093
|
+
},
|
|
1094
|
+
get editMode() {
|
|
1095
|
+
return live.editMode;
|
|
1096
|
+
}
|
|
1045
1097
|
});
|
|
1046
1098
|
function fireLifecycle(viewName, hook, extra) {
|
|
1047
1099
|
const view = normalized.views[viewName];
|
|
@@ -1068,7 +1120,7 @@ function createRuntime(dsl, options = {}) {
|
|
|
1068
1120
|
return;
|
|
1069
1121
|
}
|
|
1070
1122
|
pending.push(
|
|
1071
|
-
Promise.resolve(source(ref.params || {}, user)).then((result) => {
|
|
1123
|
+
Promise.resolve(source(ref.params || {}, live.user)).then((result) => {
|
|
1072
1124
|
resolved[key] = result;
|
|
1073
1125
|
})
|
|
1074
1126
|
);
|
|
@@ -1106,6 +1158,12 @@ function createRuntime(dsl, options = {}) {
|
|
|
1106
1158
|
}
|
|
1107
1159
|
return {
|
|
1108
1160
|
getTree,
|
|
1161
|
+
update(patch) {
|
|
1162
|
+
if (patch.user !== void 0) live.user = patch.user;
|
|
1163
|
+
if (patch.handlers !== void 0) live.handlers = patch.handlers;
|
|
1164
|
+
if (patch.editMode !== void 0) live.editMode = patch.editMode;
|
|
1165
|
+
notify();
|
|
1166
|
+
},
|
|
1109
1167
|
subscribe(listener) {
|
|
1110
1168
|
listeners.add(listener);
|
|
1111
1169
|
return () => listeners.delete(listener);
|
|
@@ -1184,7 +1242,10 @@ function validate(dsl) {
|
|
|
1184
1242
|
}
|
|
1185
1243
|
const doc = dsl;
|
|
1186
1244
|
if (doc.version !== DSL_VERSION) {
|
|
1187
|
-
add(
|
|
1245
|
+
add(
|
|
1246
|
+
"version",
|
|
1247
|
+
`version \u5FC5\u987B\u662F ${DSL_VERSION}\uFF0C\u5F53\u524D\u4E3A ${JSON.stringify(doc.version)}`
|
|
1248
|
+
);
|
|
1188
1249
|
}
|
|
1189
1250
|
const dataKeys = validateData(doc.data, add);
|
|
1190
1251
|
const stateKeys = validateState(doc.state, add);
|
|
@@ -1284,7 +1345,10 @@ function validateStage(stage, add, warn, ctx, prefix) {
|
|
|
1284
1345
|
if (stage.onShow !== void 0) validateAction(stage.onShow, at("onShow"), ctx);
|
|
1285
1346
|
if (stage.onClose !== void 0) validateAction(stage.onClose, at("onClose"), ctx);
|
|
1286
1347
|
if (stage.height === "auto" && stage.layout !== "flow") {
|
|
1287
|
-
add(
|
|
1348
|
+
add(
|
|
1349
|
+
"stage.height",
|
|
1350
|
+
"height: 'auto' \u9700\u8981\u540C\u65F6\u8BBE\u7F6E stage.layout: 'flow'\uFF0C\u5426\u5219\u9AD8\u5EA6\u4F1A\u584C\u6210 0"
|
|
1351
|
+
);
|
|
1288
1352
|
}
|
|
1289
1353
|
}
|
|
1290
1354
|
function validateCloseButton(config, add, warn) {
|
|
@@ -1298,7 +1362,10 @@ function validateCloseButton(config, add, warn) {
|
|
|
1298
1362
|
}
|
|
1299
1363
|
if (config.offset !== void 0) {
|
|
1300
1364
|
if (!Array.isArray(config.offset) || config.offset.length !== 2) {
|
|
1301
|
-
add(
|
|
1365
|
+
add(
|
|
1366
|
+
"stage.closeButton.offset",
|
|
1367
|
+
"offset \u5FC5\u987B\u662F [x, y] \u4E24\u9879\u6570\u7EC4\uFF0C\u8D1F\u503C\u4F1A\u628A\u6309\u94AE\u79FB\u5230\u5F39\u7A97\u5916\u9762"
|
|
1368
|
+
);
|
|
1302
1369
|
} else {
|
|
1303
1370
|
config.offset.forEach((value, index) => {
|
|
1304
1371
|
if (!checkLength(value)) {
|
|
@@ -1319,7 +1386,10 @@ function validateCloseButton(config, add, warn) {
|
|
|
1319
1386
|
if (config.style) {
|
|
1320
1387
|
Object.keys(config.style).forEach((key) => {
|
|
1321
1388
|
if (ALLOWED_STYLE_KEYS.indexOf(key) === -1) {
|
|
1322
|
-
warn(
|
|
1389
|
+
warn(
|
|
1390
|
+
`stage.closeButton.style.${key}`,
|
|
1391
|
+
`\u6837\u5F0F\u5C5E\u6027 "${key}" \u4E0D\u5728\u767D\u540D\u5355\u5185\uFF0C\u6E32\u67D3\u65F6\u4F1A\u88AB\u5FFD\u7565`
|
|
1392
|
+
);
|
|
1323
1393
|
}
|
|
1324
1394
|
});
|
|
1325
1395
|
}
|
|
@@ -1390,7 +1460,10 @@ function validateNode(node, path, layout, ctx) {
|
|
|
1390
1460
|
} else {
|
|
1391
1461
|
node.rect.forEach((value, index) => {
|
|
1392
1462
|
if (value !== void 0 && value !== null && !checkLength(value)) {
|
|
1393
|
-
add(
|
|
1463
|
+
add(
|
|
1464
|
+
`${path}.rect[${index}]`,
|
|
1465
|
+
`"${value}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6\uFF0C\u652F\u6301\u6570\u5B57(px) / '100%' / 'auto' \u7B49`
|
|
1466
|
+
);
|
|
1394
1467
|
}
|
|
1395
1468
|
});
|
|
1396
1469
|
}
|
|
@@ -1408,7 +1481,10 @@ function validateNode(node, path, layout, ctx) {
|
|
|
1408
1481
|
);
|
|
1409
1482
|
}
|
|
1410
1483
|
if (key === "aspectRatio" && !checkAspectRatio(node.style[key])) {
|
|
1411
|
-
add(
|
|
1484
|
+
add(
|
|
1485
|
+
`${path}.style.aspectRatio`,
|
|
1486
|
+
"aspectRatio \u8981\u5199\u6210 '750 / 200' \u6216 1.5 \u8FD9\u6837\u7684\u5BBD\u9AD8\u6BD4"
|
|
1487
|
+
);
|
|
1412
1488
|
}
|
|
1413
1489
|
});
|
|
1414
1490
|
}
|
|
@@ -1438,7 +1514,8 @@ function validateNodeByType(node, path, ctx) {
|
|
|
1438
1514
|
if (typeof node.bind !== "string") {
|
|
1439
1515
|
add(`${path}.bind`, "tabs \u5FC5\u987B\u63D0\u4F9B bind\uFF08data \u4E2D\u7684\u6570\u7EC4\u5B57\u6BB5\u540D\uFF09");
|
|
1440
1516
|
}
|
|
1441
|
-
if (typeof node.stateKey !== "string")
|
|
1517
|
+
if (typeof node.stateKey !== "string")
|
|
1518
|
+
add(`${path}.stateKey`, "tabs \u5FC5\u987B\u63D0\u4F9B stateKey");
|
|
1442
1519
|
if (node.labelField !== void 0 && typeof node.labelField !== "string") {
|
|
1443
1520
|
add(`${path}.labelField`, "labelField \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
1444
1521
|
}
|
|
@@ -1504,7 +1581,10 @@ function validateAction(action, path, ctx) {
|
|
|
1504
1581
|
add(`${path}.view`, `views \u91CC\u4E0D\u5B58\u5728\u89C6\u56FE "${action.view}"`);
|
|
1505
1582
|
}
|
|
1506
1583
|
if (action.mode !== void 0 && ["stack", "replace"].indexOf(action.mode) === -1) {
|
|
1507
|
-
add(
|
|
1584
|
+
add(
|
|
1585
|
+
`${path}.mode`,
|
|
1586
|
+
"mode \u53EA\u80FD\u662F 'stack'\uFF08\u9ED8\u8BA4\uFF0C\u53E0\u4E00\u5C42\uFF09\u6216 'replace'\uFF08\u6362\u6389\u5F53\u524D\u5C42\uFF09"
|
|
1587
|
+
);
|
|
1508
1588
|
}
|
|
1509
1589
|
}
|
|
1510
1590
|
if (action.type === "sequence") {
|