@yaoxiu/marketing-dsl 1.0.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 +111 -0
- package/dist/index.cjs +1519 -0
- package/dist/index.d.cts +430 -0
- package/dist/index.d.ts +430 -0
- package/dist/index.js +1496 -0
- package/package.json +32 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1519 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/expression.ts
|
|
4
|
+
var KEYWORD_LITERALS = {
|
|
5
|
+
true: true,
|
|
6
|
+
false: false,
|
|
7
|
+
null: null,
|
|
8
|
+
undefined: void 0
|
|
9
|
+
};
|
|
10
|
+
var PUNCTUATORS = [
|
|
11
|
+
"===",
|
|
12
|
+
"!==",
|
|
13
|
+
"<=",
|
|
14
|
+
">=",
|
|
15
|
+
"==",
|
|
16
|
+
"!=",
|
|
17
|
+
"&&",
|
|
18
|
+
"||",
|
|
19
|
+
"(",
|
|
20
|
+
")",
|
|
21
|
+
"[",
|
|
22
|
+
"]",
|
|
23
|
+
".",
|
|
24
|
+
"?",
|
|
25
|
+
":",
|
|
26
|
+
"<",
|
|
27
|
+
">",
|
|
28
|
+
"!",
|
|
29
|
+
"+",
|
|
30
|
+
"-",
|
|
31
|
+
"*",
|
|
32
|
+
"/",
|
|
33
|
+
"%"
|
|
34
|
+
];
|
|
35
|
+
function tokenize(input) {
|
|
36
|
+
const tokens = [];
|
|
37
|
+
let i = 0;
|
|
38
|
+
while (i < input.length) {
|
|
39
|
+
const ch = input[i];
|
|
40
|
+
if (/\s/.test(ch)) {
|
|
41
|
+
i++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (ch === '"' || ch === "'") {
|
|
45
|
+
const quote = ch;
|
|
46
|
+
let value = "";
|
|
47
|
+
i++;
|
|
48
|
+
while (i < input.length && input[i] !== quote) {
|
|
49
|
+
if (input[i] === "\\") {
|
|
50
|
+
value += input[i + 1] || "";
|
|
51
|
+
i += 2;
|
|
52
|
+
} else {
|
|
53
|
+
value += input[i];
|
|
54
|
+
i++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (i >= input.length) throw new Error("\u5B57\u7B26\u4E32\u6CA1\u6709\u95ED\u5408");
|
|
58
|
+
i++;
|
|
59
|
+
tokens.push({ type: "string", value });
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (/[0-9]/.test(ch) || ch === "." && /[0-9]/.test(input[i + 1] || "")) {
|
|
63
|
+
let raw = "";
|
|
64
|
+
while (i < input.length && /[0-9.]/.test(input[i])) {
|
|
65
|
+
raw += input[i];
|
|
66
|
+
i++;
|
|
67
|
+
}
|
|
68
|
+
tokens.push({ type: "number", value: Number(raw) });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (/[A-Za-z_$]/.test(ch)) {
|
|
72
|
+
let name = "";
|
|
73
|
+
while (i < input.length && /[A-Za-z0-9_$]/.test(input[i])) {
|
|
74
|
+
name += input[i];
|
|
75
|
+
i++;
|
|
76
|
+
}
|
|
77
|
+
tokens.push({ type: "ident", value: name });
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const punctuator = PUNCTUATORS.find((p) => input.startsWith(p, i));
|
|
81
|
+
if (!punctuator) throw new Error(`\u65E0\u6CD5\u8BC6\u522B\u7684\u5B57\u7B26 "${ch}"`);
|
|
82
|
+
tokens.push({ type: "punc", value: punctuator });
|
|
83
|
+
i += punctuator.length;
|
|
84
|
+
}
|
|
85
|
+
return tokens;
|
|
86
|
+
}
|
|
87
|
+
function createParser(tokens) {
|
|
88
|
+
let pos = 0;
|
|
89
|
+
const peek = () => tokens[pos];
|
|
90
|
+
const isPunc = (v) => {
|
|
91
|
+
const token = peek();
|
|
92
|
+
return !!token && token.type === "punc" && token.value === v;
|
|
93
|
+
};
|
|
94
|
+
const eat = (v) => {
|
|
95
|
+
if (!isPunc(v)) throw new Error(`\u7F3A\u5C11 "${v}"`);
|
|
96
|
+
pos++;
|
|
97
|
+
};
|
|
98
|
+
const binaryLevels = [
|
|
99
|
+
["||"],
|
|
100
|
+
["&&"],
|
|
101
|
+
["===", "!==", "==", "!="],
|
|
102
|
+
["<", ">", "<=", ">="],
|
|
103
|
+
["+", "-"],
|
|
104
|
+
["*", "/", "%"]
|
|
105
|
+
];
|
|
106
|
+
function parseExpression() {
|
|
107
|
+
return parseTernary();
|
|
108
|
+
}
|
|
109
|
+
function parseTernary() {
|
|
110
|
+
const test = parseBinary(0);
|
|
111
|
+
if (!isPunc("?")) return test;
|
|
112
|
+
eat("?");
|
|
113
|
+
const consequent = parseTernary();
|
|
114
|
+
eat(":");
|
|
115
|
+
const alternate = parseTernary();
|
|
116
|
+
return { kind: "ternary", test, consequent, alternate };
|
|
117
|
+
}
|
|
118
|
+
function parseBinary(level) {
|
|
119
|
+
if (level >= binaryLevels.length) return parseUnary();
|
|
120
|
+
let left = parseBinary(level + 1);
|
|
121
|
+
for (; ; ) {
|
|
122
|
+
const token = peek();
|
|
123
|
+
if (!token || token.type !== "punc") break;
|
|
124
|
+
if (binaryLevels[level].indexOf(String(token.value)) === -1) break;
|
|
125
|
+
const operator = String(token.value);
|
|
126
|
+
pos++;
|
|
127
|
+
const right = parseBinary(level + 1);
|
|
128
|
+
left = { kind: "binary", operator, left, right };
|
|
129
|
+
}
|
|
130
|
+
return left;
|
|
131
|
+
}
|
|
132
|
+
function parseUnary() {
|
|
133
|
+
if (isPunc("!") || isPunc("-")) {
|
|
134
|
+
const operator = String(peek().value);
|
|
135
|
+
pos++;
|
|
136
|
+
return { kind: "unary", operator, argument: parseUnary() };
|
|
137
|
+
}
|
|
138
|
+
return parseMember();
|
|
139
|
+
}
|
|
140
|
+
function parseMember() {
|
|
141
|
+
let object = parsePrimary();
|
|
142
|
+
for (; ; ) {
|
|
143
|
+
if (isPunc(".")) {
|
|
144
|
+
eat(".");
|
|
145
|
+
const token = peek();
|
|
146
|
+
if (!token || token.type !== "ident") throw new Error('"." \u540E\u9762\u9700\u8981\u5C5E\u6027\u540D');
|
|
147
|
+
pos++;
|
|
148
|
+
object = {
|
|
149
|
+
kind: "member",
|
|
150
|
+
object,
|
|
151
|
+
property: { kind: "literal", value: token.value }
|
|
152
|
+
};
|
|
153
|
+
} else if (isPunc("[")) {
|
|
154
|
+
eat("[");
|
|
155
|
+
const property = parseExpression();
|
|
156
|
+
eat("]");
|
|
157
|
+
object = { kind: "member", object, property };
|
|
158
|
+
} else {
|
|
159
|
+
return object;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function parsePrimary() {
|
|
164
|
+
const token = peek();
|
|
165
|
+
if (!token) throw new Error("\u8868\u8FBE\u5F0F\u4E0D\u5B8C\u6574");
|
|
166
|
+
if (isPunc("(")) {
|
|
167
|
+
eat("(");
|
|
168
|
+
const node = parseExpression();
|
|
169
|
+
eat(")");
|
|
170
|
+
return node;
|
|
171
|
+
}
|
|
172
|
+
if (token.type === "number" || token.type === "string") {
|
|
173
|
+
pos++;
|
|
174
|
+
return { kind: "literal", value: token.value };
|
|
175
|
+
}
|
|
176
|
+
if (token.type === "ident") {
|
|
177
|
+
pos++;
|
|
178
|
+
const name = String(token.value);
|
|
179
|
+
if (Object.prototype.hasOwnProperty.call(KEYWORD_LITERALS, name)) {
|
|
180
|
+
return { kind: "literal", value: KEYWORD_LITERALS[name] };
|
|
181
|
+
}
|
|
182
|
+
return { kind: "identifier", name };
|
|
183
|
+
}
|
|
184
|
+
throw new Error(`\u610F\u5916\u7684\u7B26\u53F7 "${token.value}"`);
|
|
185
|
+
}
|
|
186
|
+
const ast = parseExpression();
|
|
187
|
+
if (pos < tokens.length) throw new Error(`\u8868\u8FBE\u5F0F\u7ED3\u5C3E\u6709\u591A\u4F59\u5185\u5BB9 "${tokens[pos].value}"`);
|
|
188
|
+
return ast;
|
|
189
|
+
}
|
|
190
|
+
var astCache = /* @__PURE__ */ new Map();
|
|
191
|
+
function parse(source) {
|
|
192
|
+
const cached = astCache.get(source);
|
|
193
|
+
if (cached) return cached;
|
|
194
|
+
const ast = createParser(tokenize(source));
|
|
195
|
+
astCache.set(source, ast);
|
|
196
|
+
return ast;
|
|
197
|
+
}
|
|
198
|
+
var BLOCKED_KEYS = ["__proto__", "constructor", "prototype"];
|
|
199
|
+
function readProperty(object, key) {
|
|
200
|
+
if (object === null || object === void 0) return void 0;
|
|
201
|
+
if (BLOCKED_KEYS.indexOf(String(key)) > -1) return void 0;
|
|
202
|
+
const value = object[String(key)];
|
|
203
|
+
return typeof value === "function" ? void 0 : value;
|
|
204
|
+
}
|
|
205
|
+
function evaluateNode(node, context) {
|
|
206
|
+
switch (node.kind) {
|
|
207
|
+
case "literal":
|
|
208
|
+
return node.value;
|
|
209
|
+
case "identifier":
|
|
210
|
+
return readProperty(context, node.name);
|
|
211
|
+
case "member":
|
|
212
|
+
return readProperty(
|
|
213
|
+
evaluateNode(node.object, context),
|
|
214
|
+
evaluateNode(node.property, context)
|
|
215
|
+
);
|
|
216
|
+
case "unary": {
|
|
217
|
+
const value = evaluateNode(node.argument, context);
|
|
218
|
+
return node.operator === "!" ? !value : -value;
|
|
219
|
+
}
|
|
220
|
+
case "ternary":
|
|
221
|
+
return evaluateNode(node.test, context) ? evaluateNode(node.consequent, context) : evaluateNode(node.alternate, context);
|
|
222
|
+
case "binary": {
|
|
223
|
+
if (node.operator === "&&") {
|
|
224
|
+
return evaluateNode(node.left, context) && evaluateNode(node.right, context);
|
|
225
|
+
}
|
|
226
|
+
if (node.operator === "||") {
|
|
227
|
+
return evaluateNode(node.left, context) || evaluateNode(node.right, context);
|
|
228
|
+
}
|
|
229
|
+
const left = evaluateNode(node.left, context);
|
|
230
|
+
const right = evaluateNode(node.right, context);
|
|
231
|
+
switch (node.operator) {
|
|
232
|
+
case "===":
|
|
233
|
+
return left === right;
|
|
234
|
+
case "!==":
|
|
235
|
+
return left !== right;
|
|
236
|
+
case "==":
|
|
237
|
+
return left == right;
|
|
238
|
+
case "!=":
|
|
239
|
+
return left != right;
|
|
240
|
+
case "<":
|
|
241
|
+
return left < right;
|
|
242
|
+
case ">":
|
|
243
|
+
return left > right;
|
|
244
|
+
case "<=":
|
|
245
|
+
return left <= right;
|
|
246
|
+
case ">=":
|
|
247
|
+
return left >= right;
|
|
248
|
+
case "+":
|
|
249
|
+
return left + right;
|
|
250
|
+
case "-":
|
|
251
|
+
return left - right;
|
|
252
|
+
case "*":
|
|
253
|
+
return left * right;
|
|
254
|
+
case "/":
|
|
255
|
+
return left / right;
|
|
256
|
+
case "%":
|
|
257
|
+
return left % right;
|
|
258
|
+
default:
|
|
259
|
+
throw new Error(`\u4E0D\u652F\u6301\u7684\u8FD0\u7B97\u7B26 "${node.operator}"`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
default:
|
|
263
|
+
throw new Error("\u4E0D\u652F\u6301\u7684\u8282\u70B9\u7C7B\u578B");
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function evaluate(source, context) {
|
|
267
|
+
try {
|
|
268
|
+
return evaluateNode(parse(source), context);
|
|
269
|
+
} catch (e) {
|
|
270
|
+
return void 0;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
function check(source) {
|
|
274
|
+
try {
|
|
275
|
+
parse(source);
|
|
276
|
+
return "";
|
|
277
|
+
} catch (error) {
|
|
278
|
+
return error.message;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
var INTERPOLATION_RE = /\{\{([\s\S]+?)\}\}/g;
|
|
282
|
+
function interpolate(input, context) {
|
|
283
|
+
if (typeof input !== "string" || input.indexOf("{{") === -1) return input;
|
|
284
|
+
const trimmed = input.trim();
|
|
285
|
+
const tokens = trimmed.match(INTERPOLATION_RE) || [];
|
|
286
|
+
if (tokens.length === 1 && tokens[0] === trimmed) {
|
|
287
|
+
return evaluate(trimmed.slice(2, -2).trim(), context);
|
|
288
|
+
}
|
|
289
|
+
return input.replace(INTERPOLATION_RE, (_, expr) => {
|
|
290
|
+
const value = evaluate(expr.trim(), context);
|
|
291
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
function interpolateDeep(input, context) {
|
|
295
|
+
if (typeof input === "string") return interpolate(input, context);
|
|
296
|
+
if (Array.isArray(input)) {
|
|
297
|
+
return input.map((item) => interpolateDeep(item, context));
|
|
298
|
+
}
|
|
299
|
+
if (input && typeof input === "object") {
|
|
300
|
+
const result = {};
|
|
301
|
+
Object.keys(input).forEach((key) => {
|
|
302
|
+
result[key] = interpolateDeep(input[key], context);
|
|
303
|
+
});
|
|
304
|
+
return result;
|
|
305
|
+
}
|
|
306
|
+
return input;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/url.ts
|
|
310
|
+
var SAFE_PROTOCOLS = ["http:", "https:", "mailto:", "tel:"];
|
|
311
|
+
function safeUrl(input) {
|
|
312
|
+
if (typeof input !== "string") return "";
|
|
313
|
+
const value = input.trim();
|
|
314
|
+
if (!value) return "";
|
|
315
|
+
if (/^(\/|\.{1,2}\/|#|\?)/.test(value)) return value;
|
|
316
|
+
const matched = value.match(/^([a-zA-Z][a-zA-Z0-9+.-]*:)/);
|
|
317
|
+
if (!matched) return value;
|
|
318
|
+
return SAFE_PROTOCOLS.indexOf(matched[1].toLowerCase()) > -1 ? value : "";
|
|
319
|
+
}
|
|
320
|
+
function safeImageUrl(input) {
|
|
321
|
+
if (typeof input === "string" && /^data:image\//i.test(input.trim())) {
|
|
322
|
+
return input.trim();
|
|
323
|
+
}
|
|
324
|
+
return safeUrl(input);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// src/actions.ts
|
|
328
|
+
function createDispatcher(options) {
|
|
329
|
+
const { setState, emit, openView, closeTop, closeAll, handlers = {}, editMode = false } = options;
|
|
330
|
+
function dispatch(action, context) {
|
|
331
|
+
if (!action || !action.type) return;
|
|
332
|
+
switch (action.type) {
|
|
333
|
+
case "sequence":
|
|
334
|
+
(action.actions || []).forEach((item) => dispatch(item, context));
|
|
335
|
+
return;
|
|
336
|
+
case "navigate": {
|
|
337
|
+
const url = safeUrl(interpolate(action.url, context));
|
|
338
|
+
if (!url) {
|
|
339
|
+
emit("error", { type: "unsafe-url", raw: action.url });
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const target = action.target || "_blank";
|
|
343
|
+
emit("navigate", { url, target });
|
|
344
|
+
if (editMode) return;
|
|
345
|
+
if (typeof window === "undefined") return;
|
|
346
|
+
if (target === "_self") window.location.href = url;
|
|
347
|
+
else window.open(url, "_blank", "noopener,noreferrer");
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
// 打开另一个视图:stack 叠一层(下面那层还在)、replace 换掉当前层
|
|
351
|
+
case "open":
|
|
352
|
+
emit("open", { view: action.view, mode: action.mode || "stack" });
|
|
353
|
+
openView(action.view, action.mode);
|
|
354
|
+
return;
|
|
355
|
+
// 关掉栈顶那层;只剩一层时就是整体关闭
|
|
356
|
+
case "close":
|
|
357
|
+
closeTop(action.reason);
|
|
358
|
+
return;
|
|
359
|
+
case "closeAll":
|
|
360
|
+
closeAll(action.reason);
|
|
361
|
+
return;
|
|
362
|
+
case "setState":
|
|
363
|
+
setState(action.key, interpolate(action.value, context));
|
|
364
|
+
return;
|
|
365
|
+
case "track":
|
|
366
|
+
emit("track", {
|
|
367
|
+
event: String(interpolate(action.event, context)),
|
|
368
|
+
params: interpolateDeep(action.params || {}, context)
|
|
369
|
+
});
|
|
370
|
+
return;
|
|
371
|
+
case "call": {
|
|
372
|
+
const handler = handlers[action.name];
|
|
373
|
+
if (typeof handler !== "function") {
|
|
374
|
+
emit("error", { type: "unknown-handler", name: action.name });
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const params = interpolateDeep(action.params || {}, context);
|
|
378
|
+
emit("call", { name: action.name, params });
|
|
379
|
+
if (editMode) return;
|
|
380
|
+
handler(params);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
default:
|
|
384
|
+
emit("error", {
|
|
385
|
+
type: "unknown-action",
|
|
386
|
+
action: action.type
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return dispatch;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/countdown.ts
|
|
394
|
+
function pad(value, len = 2) {
|
|
395
|
+
return String(value).padStart(len, "0");
|
|
396
|
+
}
|
|
397
|
+
function parseEndTime(to) {
|
|
398
|
+
if (typeof to === "number") return to;
|
|
399
|
+
if (to === void 0 || to === null || to === "") return 0;
|
|
400
|
+
const raw = String(to).trim();
|
|
401
|
+
if (/^\d+$/.test(raw)) return Number(raw);
|
|
402
|
+
const time = new Date(raw.replace(/-/g, "/")).getTime();
|
|
403
|
+
return isNaN(time) ? 0 : time;
|
|
404
|
+
}
|
|
405
|
+
function computeParts(endTime, now = Date.now()) {
|
|
406
|
+
const raw = endTime - now;
|
|
407
|
+
const remain = raw > 0 ? raw : 0;
|
|
408
|
+
const cs = Math.floor(remain / 10);
|
|
409
|
+
const sec = Math.floor(cs / 100);
|
|
410
|
+
return {
|
|
411
|
+
d: String(Math.floor(sec / 86400)),
|
|
412
|
+
// h 是 0~23;跨天还想连着数小时的用 hAll(如 143:58:07)
|
|
413
|
+
h: pad(Math.floor(sec % 86400 / 3600)),
|
|
414
|
+
hAll: pad(Math.floor(sec / 3600)),
|
|
415
|
+
m: pad(Math.floor(sec % 3600 / 60)),
|
|
416
|
+
s: pad(sec % 60),
|
|
417
|
+
cs: pad(cs % 100),
|
|
418
|
+
ended: !endTime || remain <= 0
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function formatParts(parts, format) {
|
|
422
|
+
return (format || "{d}\u5929 {h}:{m}:{s}").replace("{d}", parts.d).replace("{hAll}", parts.hAll).replace("{h}", parts.h).replace("{m}", parts.m).replace("{s}", parts.s).replace("{cs}", parts.cs);
|
|
423
|
+
}
|
|
424
|
+
function createTicker(options) {
|
|
425
|
+
let timer = null;
|
|
426
|
+
let raf = 0;
|
|
427
|
+
let running = false;
|
|
428
|
+
let precision = "s";
|
|
429
|
+
let endTimes = [];
|
|
430
|
+
const hasRaf = typeof requestAnimationFrame === "function";
|
|
431
|
+
function allEnded() {
|
|
432
|
+
const now = Date.now();
|
|
433
|
+
return endTimes.every((end) => !end || end - now <= 0);
|
|
434
|
+
}
|
|
435
|
+
function tick() {
|
|
436
|
+
options.onTick();
|
|
437
|
+
if (allEnded()) {
|
|
438
|
+
stop();
|
|
439
|
+
if (options.onAllEnded) options.onAllEnded();
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
function stop() {
|
|
443
|
+
running = false;
|
|
444
|
+
if (timer) {
|
|
445
|
+
clearInterval(timer);
|
|
446
|
+
timer = null;
|
|
447
|
+
}
|
|
448
|
+
if (raf) {
|
|
449
|
+
cancelAnimationFrame(raf);
|
|
450
|
+
raf = 0;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function sync(nextEndTimes, nextPrecision) {
|
|
454
|
+
const changed = nextPrecision !== precision || nextEndTimes.length !== endTimes.length || nextEndTimes.some((end, i) => end !== endTimes[i]);
|
|
455
|
+
endTimes = nextEndTimes;
|
|
456
|
+
precision = nextPrecision;
|
|
457
|
+
if (!changed && running) return;
|
|
458
|
+
stop();
|
|
459
|
+
if (!endTimes.length || allEnded()) return;
|
|
460
|
+
running = true;
|
|
461
|
+
if (precision === "cs" && hasRaf) {
|
|
462
|
+
const loop = () => {
|
|
463
|
+
tick();
|
|
464
|
+
if (running) raf = requestAnimationFrame(loop);
|
|
465
|
+
};
|
|
466
|
+
raf = requestAnimationFrame(loop);
|
|
467
|
+
} else {
|
|
468
|
+
timer = setInterval(tick, 1e3);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
return { sync, stop };
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/views.ts
|
|
475
|
+
var SINGLE_VIEW_NAME = "main";
|
|
476
|
+
function normalizeViews(dsl) {
|
|
477
|
+
if (!dsl || typeof dsl !== "object") {
|
|
478
|
+
return { entry: SINGLE_VIEW_NAME, views: {}, isMulti: false };
|
|
479
|
+
}
|
|
480
|
+
if (dsl.views && typeof dsl.views === "object" && !Array.isArray(dsl.views)) {
|
|
481
|
+
const names = Object.keys(dsl.views);
|
|
482
|
+
return {
|
|
483
|
+
entry: dsl.entry && dsl.views[dsl.entry] ? dsl.entry : names[0],
|
|
484
|
+
views: dsl.views,
|
|
485
|
+
isMulti: true
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
entry: SINGLE_VIEW_NAME,
|
|
490
|
+
views: {
|
|
491
|
+
[SINGLE_VIEW_NAME]: {
|
|
492
|
+
type: dsl.type,
|
|
493
|
+
stage: dsl.stage,
|
|
494
|
+
nodes: dsl.nodes
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
isMulti: false
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// src/style.ts
|
|
502
|
+
function toLength(value) {
|
|
503
|
+
return typeof value === "number" ? `${value}px` : String(value);
|
|
504
|
+
}
|
|
505
|
+
var px = toLength;
|
|
506
|
+
var LENGTH_RE = /^-?(\d+(\.\d+)?)(px|%|em|rem|vw|vh)$/;
|
|
507
|
+
function isLength(value) {
|
|
508
|
+
if (typeof value === "number") return isFinite(value);
|
|
509
|
+
if (typeof value !== "string") return false;
|
|
510
|
+
const trimmed = value.trim();
|
|
511
|
+
if (trimmed === "auto" || trimmed === "0") return true;
|
|
512
|
+
if (/^calc\([^;{}]*\)$/.test(trimmed)) return true;
|
|
513
|
+
return LENGTH_RE.test(trimmed);
|
|
514
|
+
}
|
|
515
|
+
var STYLE_MAP = {
|
|
516
|
+
// 文字
|
|
517
|
+
color: ["color", String],
|
|
518
|
+
fontSize: ["fontSize", px],
|
|
519
|
+
fontWeight: ["fontWeight", String],
|
|
520
|
+
fontFamily: ["fontFamily", String],
|
|
521
|
+
lineHeight: ["lineHeight", (v) => typeof v === "number" && v > 4 ? px(v) : String(v)],
|
|
522
|
+
letterSpacing: ["letterSpacing", px],
|
|
523
|
+
textAlign: ["textAlign", String],
|
|
524
|
+
textDecoration: ["textDecoration", String],
|
|
525
|
+
whiteSpace: ["whiteSpace", String],
|
|
526
|
+
// 倒计时这类逐帧变化的数字必须等宽,否则数字会左右抖动
|
|
527
|
+
fontVariantNumeric: ["fontVariantNumeric", String],
|
|
528
|
+
// 背景
|
|
529
|
+
background: ["background", String],
|
|
530
|
+
backgroundImage: ["backgroundImage", (v) => `url(${JSON.stringify(String(v))})`],
|
|
531
|
+
backgroundSize: ["backgroundSize", String],
|
|
532
|
+
backgroundPosition: ["backgroundPosition", String],
|
|
533
|
+
backgroundRepeat: ["backgroundRepeat", String],
|
|
534
|
+
// 边框与圆角
|
|
535
|
+
border: ["border", String],
|
|
536
|
+
borderColor: ["borderColor", String],
|
|
537
|
+
borderWidth: ["borderWidth", px],
|
|
538
|
+
borderStyle: ["borderStyle", String],
|
|
539
|
+
radius: ["borderRadius", px],
|
|
540
|
+
// 盒模型
|
|
541
|
+
padding: ["padding", px],
|
|
542
|
+
margin: ["margin", px],
|
|
543
|
+
marginTop: ["marginTop", px],
|
|
544
|
+
marginBottom: ["marginBottom", px],
|
|
545
|
+
marginLeft: ["marginLeft", px],
|
|
546
|
+
marginRight: ["marginRight", px],
|
|
547
|
+
overflow: ["overflow", String],
|
|
548
|
+
opacity: ["opacity", String],
|
|
549
|
+
shadow: ["boxShadow", String],
|
|
550
|
+
cursor: ["cursor", String],
|
|
551
|
+
// flex 容器
|
|
552
|
+
direction: ["flexDirection", (v) => v === "column" ? "column" : "row"],
|
|
553
|
+
gap: ["gap", px],
|
|
554
|
+
wrap: ["flexWrap", (v) => v ? "wrap" : "nowrap"],
|
|
555
|
+
align: ["alignItems", String],
|
|
556
|
+
justify: ["justifyContent", String],
|
|
557
|
+
// flex 子项
|
|
558
|
+
flex: ["flex", String],
|
|
559
|
+
// 不写时子项会被拉满父级交叉轴,标签这类「按内容宽度」的元素要显式声明
|
|
560
|
+
alignSelf: ["alignSelf", String],
|
|
561
|
+
width: ["width", px],
|
|
562
|
+
height: ["height", px],
|
|
563
|
+
minWidth: ["minWidth", px],
|
|
564
|
+
minHeight: ["minHeight", px],
|
|
565
|
+
maxWidth: ["maxWidth", px],
|
|
566
|
+
maxHeight: ["maxHeight", px],
|
|
567
|
+
// 图片
|
|
568
|
+
objectFit: ["objectFit", String],
|
|
569
|
+
// 宽度给百分比、高度按比例算。整图 banner 的热区必须靠它才能用百分比定位:
|
|
570
|
+
// 容器没有真实高度时,子节点的百分比 rect 算出来全是 0。
|
|
571
|
+
aspectRatio: ["aspectRatio", String]
|
|
572
|
+
};
|
|
573
|
+
var ALLOWED_STYLE_KEYS = Object.keys(STYLE_MAP);
|
|
574
|
+
function toCssStyle(style) {
|
|
575
|
+
const css = {};
|
|
576
|
+
if (!style) return css;
|
|
577
|
+
Object.keys(style).forEach((key) => {
|
|
578
|
+
const rule = STYLE_MAP[key];
|
|
579
|
+
if (!rule) return;
|
|
580
|
+
const value = style[key];
|
|
581
|
+
if (value === void 0 || value === null || value === "") return;
|
|
582
|
+
css[rule[0]] = rule[1](value);
|
|
583
|
+
});
|
|
584
|
+
return css;
|
|
585
|
+
}
|
|
586
|
+
function toRectStyle(rect) {
|
|
587
|
+
if (!Array.isArray(rect)) return {};
|
|
588
|
+
const [x, y, w, h] = rect;
|
|
589
|
+
const css = { position: "absolute", left: px(x || 0), top: px(y || 0) };
|
|
590
|
+
if (w !== void 0 && w !== null && w !== "auto") css.width = px(w);
|
|
591
|
+
if (h !== void 0 && h !== null && h !== "auto") css.height = px(h);
|
|
592
|
+
return css;
|
|
593
|
+
}
|
|
594
|
+
function resolveNodeStyle(node, layout) {
|
|
595
|
+
return Object.assign(
|
|
596
|
+
{},
|
|
597
|
+
layout === "absolute" ? toRectStyle(node.rect) : {},
|
|
598
|
+
toCssStyle(node.style)
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// src/resolve.ts
|
|
603
|
+
function resolveTree(input) {
|
|
604
|
+
const { normalized, viewStack, ready } = input;
|
|
605
|
+
const layers = viewStack.map((name) => ({ name, view: normalized.views[name] })).filter((item) => !!item.view);
|
|
606
|
+
const hasPopup = Object.keys(normalized.views).some(
|
|
607
|
+
(name) => normalized.views[name].type === "popup"
|
|
608
|
+
);
|
|
609
|
+
const countdowns = { endTimes: [], precision: "s" };
|
|
610
|
+
const renderLayers = ready ? layers.map(
|
|
611
|
+
(item, index) => resolveLayer(item.name, item.view, index === layers.length - 1, input, countdowns)
|
|
612
|
+
) : [];
|
|
613
|
+
return {
|
|
614
|
+
ready: ready && layers.length > 0,
|
|
615
|
+
hasPopup,
|
|
616
|
+
// 配置里有弹窗视图时,根容器要撑满宿主容器,弹窗层才有地方铺
|
|
617
|
+
rootStyle: hasPopup ? { position: "absolute", top: "0", right: "0", bottom: "0", left: "0" } : { position: "relative" },
|
|
618
|
+
layers: renderLayers,
|
|
619
|
+
countdownEndTimes: countdowns.endTimes,
|
|
620
|
+
countdownPrecision: countdowns.precision
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
function resolveLayer(name, view, isTop, input, countdowns) {
|
|
624
|
+
const { context, closeTop } = input;
|
|
625
|
+
const stage = view.stage || {};
|
|
626
|
+
const isPopup = view.type === "popup";
|
|
627
|
+
const rootLayout = stage.layout === "flow" ? "flow" : "absolute";
|
|
628
|
+
const maskClosable = !!stage.maskClosable;
|
|
629
|
+
const onMaskClick = maskClosable ? () => closeTop("mask") : void 0;
|
|
630
|
+
const nodes = (view.nodes || []).map((node, index) => resolveNode(node, `${name}-${index}`, rootLayout, context, input, countdowns)).filter((el) => !!el);
|
|
631
|
+
return {
|
|
632
|
+
name,
|
|
633
|
+
type: view.type,
|
|
634
|
+
isTop,
|
|
635
|
+
// 只有栈顶那层画遮罩,否则两层遮罩叠加会明显变黑
|
|
636
|
+
mask: isPopup && stage.mask !== false && isTop,
|
|
637
|
+
onMaskClick,
|
|
638
|
+
layerStyle: layerStyle(isPopup, isTop),
|
|
639
|
+
maskStyle: {
|
|
640
|
+
position: "absolute",
|
|
641
|
+
top: "0",
|
|
642
|
+
right: "0",
|
|
643
|
+
bottom: "0",
|
|
644
|
+
left: "0",
|
|
645
|
+
background: "rgba(0, 0, 0, 0.55)"
|
|
646
|
+
},
|
|
647
|
+
scrollStyle: scrollStyle(isPopup),
|
|
648
|
+
stageStyle: resolveStageStyle(stage, isPopup, context),
|
|
649
|
+
clipStyle: {
|
|
650
|
+
position: "relative",
|
|
651
|
+
height: "100%",
|
|
652
|
+
borderRadius: "inherit",
|
|
653
|
+
overflow: "hidden",
|
|
654
|
+
boxSizing: "border-box"
|
|
655
|
+
},
|
|
656
|
+
closeButton: resolveCloseButton(stage.closeButton, name, closeTop),
|
|
657
|
+
nodes
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function layerStyle(isPopup, isTop) {
|
|
661
|
+
const style = isPopup ? { position: "absolute", top: "0", right: "0", bottom: "0", left: "0" } : {};
|
|
662
|
+
if (!isTop) style.pointerEvents = "none";
|
|
663
|
+
return style;
|
|
664
|
+
}
|
|
665
|
+
function scrollStyle(isPopup) {
|
|
666
|
+
if (!isPopup) return {};
|
|
667
|
+
return {
|
|
668
|
+
position: "absolute",
|
|
669
|
+
top: "0",
|
|
670
|
+
right: "0",
|
|
671
|
+
bottom: "0",
|
|
672
|
+
left: "0",
|
|
673
|
+
display: "flex",
|
|
674
|
+
overflow: "auto",
|
|
675
|
+
padding: "20px",
|
|
676
|
+
boxSizing: "border-box"
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
function resolveStageStyle(stage, isPopup, context) {
|
|
680
|
+
const base = {
|
|
681
|
+
position: "relative",
|
|
682
|
+
boxSizing: "border-box",
|
|
683
|
+
background: "#fff"
|
|
684
|
+
};
|
|
685
|
+
if (isPopup) {
|
|
686
|
+
base.margin = "auto";
|
|
687
|
+
base.flex = "none";
|
|
688
|
+
}
|
|
689
|
+
return Object.assign(
|
|
690
|
+
base,
|
|
691
|
+
{
|
|
692
|
+
// width / height 支持数字(px)、'100%' / 'auto' / calc(...) 等相对单位,也支持 {{ }}
|
|
693
|
+
width: toLength(interpolate(stage.width === void 0 ? 320 : stage.width, context)),
|
|
694
|
+
height: toLength(interpolate(stage.height === void 0 ? 400 : stage.height, context))
|
|
695
|
+
},
|
|
696
|
+
// stage.style 也走一遍插值,这样切 tab 时弹窗本身的背景能跟着变
|
|
697
|
+
toCssStyle(interpolateDeep(stage.style, context))
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
function resolveCloseButton(config, layerName, closeTop) {
|
|
701
|
+
if (!config || config.show === false) return void 0;
|
|
702
|
+
const offset = config.offset || [8, 8];
|
|
703
|
+
const size = config.size === void 0 ? 26 : config.size;
|
|
704
|
+
const position = config.position || "top-right";
|
|
705
|
+
const style = {
|
|
706
|
+
position: "absolute",
|
|
707
|
+
width: toLength(size),
|
|
708
|
+
height: toLength(size),
|
|
709
|
+
lineHeight: toLength(size),
|
|
710
|
+
display: "flex",
|
|
711
|
+
alignItems: "center",
|
|
712
|
+
justifyContent: "center",
|
|
713
|
+
boxSizing: "border-box",
|
|
714
|
+
borderRadius: "50%",
|
|
715
|
+
background: "rgba(0, 0, 0, 0.35)",
|
|
716
|
+
color: "#fff",
|
|
717
|
+
fontSize: "18px",
|
|
718
|
+
textAlign: "center",
|
|
719
|
+
cursor: "pointer",
|
|
720
|
+
userSelect: "none",
|
|
721
|
+
zIndex: "1"
|
|
722
|
+
};
|
|
723
|
+
if (position.indexOf("bottom") === 0) style.bottom = toLength(offset[1]);
|
|
724
|
+
else style.top = toLength(offset[1]);
|
|
725
|
+
if (position.indexOf("center") > -1) {
|
|
726
|
+
style.left = "50%";
|
|
727
|
+
style.transform = "translateX(-50%)";
|
|
728
|
+
} else if (position.indexOf("left") > -1) {
|
|
729
|
+
style.left = toLength(offset[0]);
|
|
730
|
+
} else {
|
|
731
|
+
style.right = toLength(offset[0]);
|
|
732
|
+
}
|
|
733
|
+
Object.assign(style, toCssStyle(config.style));
|
|
734
|
+
const onClick = () => closeTop("close-button");
|
|
735
|
+
const image = config.image ? safeImageUrl(config.image) : "";
|
|
736
|
+
if (image) {
|
|
737
|
+
return {
|
|
738
|
+
key: `${layerName}-close`,
|
|
739
|
+
tag: "div",
|
|
740
|
+
style,
|
|
741
|
+
onClick,
|
|
742
|
+
children: [
|
|
743
|
+
{
|
|
744
|
+
key: `${layerName}-close-img`,
|
|
745
|
+
tag: "img",
|
|
746
|
+
src: image,
|
|
747
|
+
style: { width: "100%", height: "100%", objectFit: "contain", display: "block" }
|
|
748
|
+
}
|
|
749
|
+
]
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
return {
|
|
753
|
+
key: `${layerName}-close`,
|
|
754
|
+
tag: "div",
|
|
755
|
+
style,
|
|
756
|
+
onClick,
|
|
757
|
+
text: config.icon || "\xD7"
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
var BASE_STYLE = { boxSizing: "border-box" };
|
|
761
|
+
function resolveNode(node, key, layout, context, input, countdowns) {
|
|
762
|
+
if (!node || typeof node !== "object") return void 0;
|
|
763
|
+
if (node.visibleWhen && !evaluate(node.visibleWhen, context)) return void 0;
|
|
764
|
+
const style = Object.assign(
|
|
765
|
+
{},
|
|
766
|
+
BASE_STYLE,
|
|
767
|
+
resolveNodeStyle(
|
|
768
|
+
{ rect: node.rect, style: interpolateDeep(node.style, context) },
|
|
769
|
+
layout
|
|
770
|
+
)
|
|
771
|
+
);
|
|
772
|
+
const onClick = node.action ? () => input.dispatch(node.action, context) : void 0;
|
|
773
|
+
if (onClick && !style.cursor) style.cursor = "pointer";
|
|
774
|
+
switch (node.type) {
|
|
775
|
+
case "box":
|
|
776
|
+
return {
|
|
777
|
+
key,
|
|
778
|
+
tag: "div",
|
|
779
|
+
style: Object.assign({ position: "relative" }, style),
|
|
780
|
+
onClick,
|
|
781
|
+
children: resolveChildren(node.children, key, "absolute", context, input, countdowns)
|
|
782
|
+
};
|
|
783
|
+
case "flex":
|
|
784
|
+
return {
|
|
785
|
+
key,
|
|
786
|
+
tag: "div",
|
|
787
|
+
style: Object.assign({ display: "flex" }, style),
|
|
788
|
+
onClick,
|
|
789
|
+
children: resolveChildren(node.children, key, "flow", context, input, countdowns)
|
|
790
|
+
};
|
|
791
|
+
case "repeat":
|
|
792
|
+
return {
|
|
793
|
+
key,
|
|
794
|
+
tag: "div",
|
|
795
|
+
style: Object.assign({ display: "flex" }, style),
|
|
796
|
+
onClick,
|
|
797
|
+
children: resolveRepeat(node, key, context, input, countdowns)
|
|
798
|
+
};
|
|
799
|
+
case "tabs":
|
|
800
|
+
return {
|
|
801
|
+
key,
|
|
802
|
+
tag: "div",
|
|
803
|
+
style: Object.assign({ display: "flex" }, style),
|
|
804
|
+
children: resolveTabs(node, key, context, input)
|
|
805
|
+
};
|
|
806
|
+
case "image":
|
|
807
|
+
return {
|
|
808
|
+
key,
|
|
809
|
+
tag: "img",
|
|
810
|
+
src: safeImageUrl(interpolate(node.src, context)),
|
|
811
|
+
style: Object.assign({ display: "block", objectFit: "cover" }, style),
|
|
812
|
+
onClick
|
|
813
|
+
};
|
|
814
|
+
case "text":
|
|
815
|
+
return {
|
|
816
|
+
key,
|
|
817
|
+
tag: "div",
|
|
818
|
+
style: Object.assign({ wordBreak: "break-word" }, style),
|
|
819
|
+
text: toText(interpolate(node.content, context)),
|
|
820
|
+
onClick
|
|
821
|
+
};
|
|
822
|
+
case "button":
|
|
823
|
+
return {
|
|
824
|
+
key,
|
|
825
|
+
tag: "div",
|
|
826
|
+
style: Object.assign(
|
|
827
|
+
{
|
|
828
|
+
display: "flex",
|
|
829
|
+
alignItems: "center",
|
|
830
|
+
justifyContent: "center",
|
|
831
|
+
textAlign: "center",
|
|
832
|
+
cursor: "pointer",
|
|
833
|
+
userSelect: "none"
|
|
834
|
+
},
|
|
835
|
+
style
|
|
836
|
+
),
|
|
837
|
+
text: toText(interpolate(node.text, context)),
|
|
838
|
+
onClick
|
|
839
|
+
};
|
|
840
|
+
case "countdown":
|
|
841
|
+
return resolveCountdown(node, key, style, context, input, countdowns);
|
|
842
|
+
// 配置比解释器新时,未知类型降级为不渲染,不阻断整个弹窗
|
|
843
|
+
default:
|
|
844
|
+
return void 0;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
function resolveChildren(children, parentKey, layout, context, input, countdowns) {
|
|
848
|
+
return (children || []).map(
|
|
849
|
+
(child, index) => resolveNode(child, `${parentKey}-${index}`, layout, context, input, countdowns)
|
|
850
|
+
).filter((el) => !!el);
|
|
851
|
+
}
|
|
852
|
+
function resolveRepeat(node, key, context, input, countdowns) {
|
|
853
|
+
const list = evaluate(node.bind || "", context);
|
|
854
|
+
if (!Array.isArray(list) || !node.template) return [];
|
|
855
|
+
return list.map((item, index) => {
|
|
856
|
+
const itemContext = Object.assign({}, context, {
|
|
857
|
+
[node.itemName || "item"]: item,
|
|
858
|
+
[node.indexName || "index"]: index
|
|
859
|
+
});
|
|
860
|
+
return resolveNode(node.template, `${key}-${index}`, "flow", itemContext, input, countdowns);
|
|
861
|
+
}).filter((el) => !!el);
|
|
862
|
+
}
|
|
863
|
+
function resolveTabs(node, key, context, input) {
|
|
864
|
+
const list = evaluate(node.bind || "", context);
|
|
865
|
+
if (!Array.isArray(list)) return [];
|
|
866
|
+
const stateKey = node.stateKey || "";
|
|
867
|
+
const active = Number(input.state[stateKey]) || 0;
|
|
868
|
+
const labelField = node.labelField || "label";
|
|
869
|
+
return list.map((item, index) => {
|
|
870
|
+
const base = node.itemStyle || {};
|
|
871
|
+
const merged = index === active ? Object.assign({}, base, node.activeItemStyle || {}) : base;
|
|
872
|
+
const label = item && typeof item === "object" ? item[labelField] : item;
|
|
873
|
+
return {
|
|
874
|
+
key: `${key}-${index}`,
|
|
875
|
+
tag: "div",
|
|
876
|
+
style: Object.assign(
|
|
877
|
+
{
|
|
878
|
+
display: "flex",
|
|
879
|
+
alignItems: "center",
|
|
880
|
+
justifyContent: "center",
|
|
881
|
+
cursor: "pointer",
|
|
882
|
+
userSelect: "none"
|
|
883
|
+
},
|
|
884
|
+
BASE_STYLE,
|
|
885
|
+
toCssStyle(interpolateDeep(merged, context))
|
|
886
|
+
),
|
|
887
|
+
text: label === void 0 || label === null ? `\u9009\u9879${index + 1}` : String(label),
|
|
888
|
+
onClick: () => {
|
|
889
|
+
input.setState(stateKey, index);
|
|
890
|
+
if (node.action) input.dispatch(node.action, context);
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
function resolveCountdown(node, key, style, context, input, countdowns) {
|
|
896
|
+
var _a;
|
|
897
|
+
const endTime = parseEndTime(interpolate(node.to, context));
|
|
898
|
+
const parts = computeParts(endTime);
|
|
899
|
+
countdowns.endTimes.push(endTime);
|
|
900
|
+
if (node.precision === "cs") countdowns.precision = "cs";
|
|
901
|
+
if (parts.ended && endTime && node.onEnd) {
|
|
902
|
+
const token = `${key}@${endTime}`;
|
|
903
|
+
if (!input.firedEnds.has(token)) {
|
|
904
|
+
input.firedEnds.add(token);
|
|
905
|
+
input.dispatch(node.onEnd, context);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
if (parts.ended && !((_a = node.children) == null ? void 0 : _a.length)) {
|
|
909
|
+
return {
|
|
910
|
+
key,
|
|
911
|
+
tag: "div",
|
|
912
|
+
style: Object.assign({ wordBreak: "break-word" }, style),
|
|
913
|
+
text: node.endText || "\u5DF2\u7ED3\u675F"
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
if (!node.children || !node.children.length) {
|
|
917
|
+
return {
|
|
918
|
+
key,
|
|
919
|
+
tag: "div",
|
|
920
|
+
style: Object.assign({ wordBreak: "break-word" }, style),
|
|
921
|
+
text: formatParts(parts, node.format)
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
const childContext = Object.assign({}, context, { [node.as || "cd"]: parts });
|
|
925
|
+
return {
|
|
926
|
+
key,
|
|
927
|
+
tag: "div",
|
|
928
|
+
style: Object.assign({ display: "flex" }, style),
|
|
929
|
+
children: resolveChildren(node.children, key, "flow", childContext, input, countdowns)
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
function toText(value) {
|
|
933
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
// src/runtime.ts
|
|
937
|
+
var noopEmit = () => {
|
|
938
|
+
};
|
|
939
|
+
function createRuntime(dsl, options = {}) {
|
|
940
|
+
const { user = {}, sources = {}, handlers = {}, editMode = false, emit = noopEmit } = options;
|
|
941
|
+
const normalized = normalizeViews(dsl);
|
|
942
|
+
let state = Object.assign({}, dsl.state || {});
|
|
943
|
+
let resolvedData = {};
|
|
944
|
+
let viewStack = [normalized.entry];
|
|
945
|
+
let loading = true;
|
|
946
|
+
let loadFailed = false;
|
|
947
|
+
let destroyed = false;
|
|
948
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
949
|
+
const firedEnds = /* @__PURE__ */ new Set();
|
|
950
|
+
const notify = () => {
|
|
951
|
+
if (destroyed) return;
|
|
952
|
+
listeners.forEach((fn) => fn());
|
|
953
|
+
};
|
|
954
|
+
const ticker = createTicker({ onTick: notify });
|
|
955
|
+
function buildContext() {
|
|
956
|
+
const context = Object.assign({}, resolvedData, { state, user });
|
|
957
|
+
const derived = dsl.derived || {};
|
|
958
|
+
Object.keys(derived).forEach((name) => {
|
|
959
|
+
const config = derived[name] || {};
|
|
960
|
+
const list = resolvedData[config.list];
|
|
961
|
+
const index = Number(state[config.indexBy]) || 0;
|
|
962
|
+
context[name] = Array.isArray(list) ? list[index] : void 0;
|
|
963
|
+
});
|
|
964
|
+
return context;
|
|
965
|
+
}
|
|
966
|
+
function setState(key, value) {
|
|
967
|
+
state = Object.assign({}, state, { [key]: value });
|
|
968
|
+
emit("state-change", Object.assign({}, state));
|
|
969
|
+
notify();
|
|
970
|
+
}
|
|
971
|
+
function openView(name, mode) {
|
|
972
|
+
if (!normalized.views[name]) {
|
|
973
|
+
emit("error", { type: "unknown-view", name });
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
if (mode === "replace") {
|
|
977
|
+
const replaced = viewStack[viewStack.length - 1];
|
|
978
|
+
fireLifecycle(replaced, "onClose", { closeReason: "replace" });
|
|
979
|
+
viewStack = viewStack.slice(0, -1).concat(name);
|
|
980
|
+
} else {
|
|
981
|
+
viewStack = viewStack.concat(name);
|
|
982
|
+
}
|
|
983
|
+
emit("view-change", { view: name, mode: mode || "stack", stack: viewStack.slice() });
|
|
984
|
+
notify();
|
|
985
|
+
fireLifecycle(name, "onShow");
|
|
986
|
+
}
|
|
987
|
+
function closeTop(reason) {
|
|
988
|
+
const why = reason || "action";
|
|
989
|
+
if (viewStack.length > 1) {
|
|
990
|
+
const closed = viewStack[viewStack.length - 1];
|
|
991
|
+
viewStack = viewStack.slice(0, -1);
|
|
992
|
+
fireLifecycle(closed, "onClose", { closeReason: why });
|
|
993
|
+
emit("view-change", {
|
|
994
|
+
view: viewStack[viewStack.length - 1],
|
|
995
|
+
mode: "close",
|
|
996
|
+
stack: viewStack.slice(),
|
|
997
|
+
closed
|
|
998
|
+
});
|
|
999
|
+
notify();
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
fireLifecycle(viewStack[0], "onClose", { closeReason: why });
|
|
1003
|
+
emit("close", { reason: why });
|
|
1004
|
+
}
|
|
1005
|
+
function closeAll(reason) {
|
|
1006
|
+
const why = reason || "action";
|
|
1007
|
+
viewStack.slice().reverse().forEach((name) => fireLifecycle(name, "onClose", { closeReason: why }));
|
|
1008
|
+
emit("close", { reason: why });
|
|
1009
|
+
}
|
|
1010
|
+
const dispatch = createDispatcher({
|
|
1011
|
+
setState,
|
|
1012
|
+
emit,
|
|
1013
|
+
openView,
|
|
1014
|
+
// 配置里 close 可以自带 reason(如 'never-remind'),要透传给埋点,不能吞掉
|
|
1015
|
+
closeTop: (reason) => closeTop(reason),
|
|
1016
|
+
closeAll: (reason) => closeAll(reason),
|
|
1017
|
+
handlers,
|
|
1018
|
+
editMode
|
|
1019
|
+
});
|
|
1020
|
+
function fireLifecycle(viewName, hook, extra) {
|
|
1021
|
+
const view = normalized.views[viewName];
|
|
1022
|
+
const action = view && view.stage && view.stage[hook];
|
|
1023
|
+
if (!action) return;
|
|
1024
|
+
dispatch(action, Object.assign({}, buildContext(), extra));
|
|
1025
|
+
}
|
|
1026
|
+
function loadData() {
|
|
1027
|
+
const data = dsl.data || {};
|
|
1028
|
+
const resolved = {};
|
|
1029
|
+
const pending = [];
|
|
1030
|
+
Object.keys(data).forEach((key) => {
|
|
1031
|
+
const value = data[key];
|
|
1032
|
+
const isSource = !!value && typeof value === "object" && !Array.isArray(value) && "$source" in value;
|
|
1033
|
+
if (!isSource) {
|
|
1034
|
+
resolved[key] = value;
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
const ref = value;
|
|
1038
|
+
const source = sources[ref.$source];
|
|
1039
|
+
if (typeof source !== "function") {
|
|
1040
|
+
emit("error", { type: "unknown-source", name: ref.$source });
|
|
1041
|
+
pending.push(Promise.reject(new Error(`\u672A\u6CE8\u518C\u7684\u6570\u636E\u6E90 ${ref.$source}`)));
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
pending.push(
|
|
1045
|
+
Promise.resolve(source(ref.params || {}, user)).then((result) => {
|
|
1046
|
+
resolved[key] = result;
|
|
1047
|
+
})
|
|
1048
|
+
);
|
|
1049
|
+
});
|
|
1050
|
+
Promise.all(pending).then(() => {
|
|
1051
|
+
if (destroyed) return;
|
|
1052
|
+
resolvedData = resolved;
|
|
1053
|
+
loading = false;
|
|
1054
|
+
notify();
|
|
1055
|
+
emit("ready", { keys: Object.keys(resolved) });
|
|
1056
|
+
fireLifecycle(normalized.entry, "onShow");
|
|
1057
|
+
}).catch((error) => {
|
|
1058
|
+
if (destroyed) return;
|
|
1059
|
+
loading = false;
|
|
1060
|
+
loadFailed = true;
|
|
1061
|
+
notify();
|
|
1062
|
+
emit("error", { type: "data-source-failed", message: error.message });
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
loadData();
|
|
1066
|
+
function getTree() {
|
|
1067
|
+
const tree = resolveTree({
|
|
1068
|
+
normalized,
|
|
1069
|
+
viewStack,
|
|
1070
|
+
context: buildContext(),
|
|
1071
|
+
state,
|
|
1072
|
+
ready: !loading && !loadFailed,
|
|
1073
|
+
dispatch,
|
|
1074
|
+
setState,
|
|
1075
|
+
closeTop,
|
|
1076
|
+
firedEnds
|
|
1077
|
+
});
|
|
1078
|
+
ticker.sync(tree.countdownEndTimes, tree.countdownPrecision);
|
|
1079
|
+
return tree;
|
|
1080
|
+
}
|
|
1081
|
+
return {
|
|
1082
|
+
getTree,
|
|
1083
|
+
subscribe(listener) {
|
|
1084
|
+
listeners.add(listener);
|
|
1085
|
+
return () => listeners.delete(listener);
|
|
1086
|
+
},
|
|
1087
|
+
getState: () => Object.assign({}, state),
|
|
1088
|
+
getViewStack: () => viewStack.slice(),
|
|
1089
|
+
destroy() {
|
|
1090
|
+
destroyed = true;
|
|
1091
|
+
ticker.stop();
|
|
1092
|
+
listeners.clear();
|
|
1093
|
+
}
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// src/validate.ts
|
|
1098
|
+
var LENGTH_STYLE_KEYS = [
|
|
1099
|
+
"width",
|
|
1100
|
+
"height",
|
|
1101
|
+
"minWidth",
|
|
1102
|
+
"minHeight",
|
|
1103
|
+
"maxWidth",
|
|
1104
|
+
"maxHeight",
|
|
1105
|
+
"radius",
|
|
1106
|
+
"padding",
|
|
1107
|
+
"margin",
|
|
1108
|
+
"marginTop",
|
|
1109
|
+
"marginBottom",
|
|
1110
|
+
"marginLeft",
|
|
1111
|
+
"marginRight",
|
|
1112
|
+
"gap",
|
|
1113
|
+
"fontSize",
|
|
1114
|
+
"letterSpacing",
|
|
1115
|
+
"borderWidth"
|
|
1116
|
+
];
|
|
1117
|
+
var DSL_VERSION = 1;
|
|
1118
|
+
var NODE_TYPES = [
|
|
1119
|
+
"box",
|
|
1120
|
+
"flex",
|
|
1121
|
+
"repeat",
|
|
1122
|
+
"image",
|
|
1123
|
+
"text",
|
|
1124
|
+
"button",
|
|
1125
|
+
"tabs",
|
|
1126
|
+
"countdown"
|
|
1127
|
+
];
|
|
1128
|
+
var ACTION_TYPES = [
|
|
1129
|
+
"navigate",
|
|
1130
|
+
"close",
|
|
1131
|
+
"closeAll",
|
|
1132
|
+
"open",
|
|
1133
|
+
"setState",
|
|
1134
|
+
"track",
|
|
1135
|
+
"call",
|
|
1136
|
+
"sequence"
|
|
1137
|
+
];
|
|
1138
|
+
var CLOSE_POSITIONS = [
|
|
1139
|
+
"top-right",
|
|
1140
|
+
"top-left",
|
|
1141
|
+
"top-center",
|
|
1142
|
+
"bottom-right",
|
|
1143
|
+
"bottom-left",
|
|
1144
|
+
"bottom-center"
|
|
1145
|
+
];
|
|
1146
|
+
var CONTAINER_TYPES = ["box", "flex"];
|
|
1147
|
+
function validate(dsl) {
|
|
1148
|
+
const errors = [];
|
|
1149
|
+
const warnings = [];
|
|
1150
|
+
const add = (path, message) => errors.push({ path, message });
|
|
1151
|
+
const warn = (path, message) => warnings.push({ path, message });
|
|
1152
|
+
if (!dsl || typeof dsl !== "object" || Array.isArray(dsl)) {
|
|
1153
|
+
return {
|
|
1154
|
+
valid: false,
|
|
1155
|
+
errors: [{ path: "", message: "\u6839\u8282\u70B9\u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61" }],
|
|
1156
|
+
warnings
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
const doc = dsl;
|
|
1160
|
+
if (doc.version !== DSL_VERSION) {
|
|
1161
|
+
add("version", `version \u5FC5\u987B\u662F ${DSL_VERSION}\uFF0C\u5F53\u524D\u4E3A ${JSON.stringify(doc.version)}`);
|
|
1162
|
+
}
|
|
1163
|
+
const dataKeys = validateData(doc.data, add);
|
|
1164
|
+
const stateKeys = validateState(doc.state, add);
|
|
1165
|
+
validateDerived(doc.derived, dataKeys, stateKeys, add);
|
|
1166
|
+
const isMulti = doc.views !== void 0;
|
|
1167
|
+
if (isMulti) validateMultiView(doc, add);
|
|
1168
|
+
const { views } = normalizeViews(doc);
|
|
1169
|
+
const viewNames = Object.keys(views);
|
|
1170
|
+
const ctx = { add, warn, viewNames };
|
|
1171
|
+
viewNames.forEach((name) => {
|
|
1172
|
+
validateView(views[name], isMulti ? `views.${name}` : "", ctx);
|
|
1173
|
+
});
|
|
1174
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
1175
|
+
}
|
|
1176
|
+
function validateMultiView(dsl, add) {
|
|
1177
|
+
if (typeof dsl.views !== "object" || dsl.views === null || Array.isArray(dsl.views)) {
|
|
1178
|
+
add("views", 'views \u5FC5\u987B\u662F\u5BF9\u8C61\uFF0C\u5F62\u5982 { "main": { type, stage, nodes } }');
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
if (!Object.keys(dsl.views).length) {
|
|
1182
|
+
add("views", "views \u81F3\u5C11\u8981\u6709\u4E00\u4E2A\u89C6\u56FE");
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
if (dsl.entry === void 0) {
|
|
1186
|
+
add("entry", "\u591A\u89C6\u56FE\u5FC5\u987B\u6307\u5B9A entry\uFF08\u9996\u4E2A\u5C55\u793A\u7684\u89C6\u56FE\u540D\uFF09");
|
|
1187
|
+
} else if (!dsl.views[dsl.entry]) {
|
|
1188
|
+
add("entry", `entry "${dsl.entry}" \u5728 views \u91CC\u4E0D\u5B58\u5728`);
|
|
1189
|
+
}
|
|
1190
|
+
["stage", "nodes"].forEach((key) => {
|
|
1191
|
+
if (dsl[key] !== void 0) {
|
|
1192
|
+
add(key, `\u7528\u4E86 views \u4E4B\u540E\uFF0C${key} \u8981\u5199\u5728\u6BCF\u4E2A\u89C6\u56FE\u91CC\uFF0C\u4E0D\u80FD\u7559\u5728\u9876\u5C42`);
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
function validateView(view, prefix, ctx) {
|
|
1197
|
+
const { add, warn } = ctx;
|
|
1198
|
+
const at = (key) => prefix ? `${prefix}.${key}` : key;
|
|
1199
|
+
if (!view || typeof view !== "object" || Array.isArray(view)) {
|
|
1200
|
+
add(prefix || "view", "\u89C6\u56FE\u5FC5\u987B\u662F\u5BF9\u8C61\uFF0C\u5305\u542B type / stage / nodes");
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
if (["popup", "banner", "notice"].indexOf(view.type) === -1) {
|
|
1204
|
+
add(at("type"), "type \u5FC5\u987B\u662F popup / banner / notice \u4E4B\u4E00");
|
|
1205
|
+
}
|
|
1206
|
+
validateStage(
|
|
1207
|
+
view.stage,
|
|
1208
|
+
(path, message) => add(prefix ? `${prefix}.${path}` : path, message),
|
|
1209
|
+
warn,
|
|
1210
|
+
ctx,
|
|
1211
|
+
prefix
|
|
1212
|
+
);
|
|
1213
|
+
if (!Array.isArray(view.nodes) || view.nodes.length === 0) {
|
|
1214
|
+
add(at("nodes"), "nodes \u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4");
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
const rootLayout = (view.stage || {}).layout === "flow" ? "flow" : "absolute";
|
|
1218
|
+
view.nodes.forEach((node, index) => {
|
|
1219
|
+
validateNode(node, at(`nodes[${index}]`), rootLayout, ctx);
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
function checkLength(value) {
|
|
1223
|
+
if (typeof value === "string" && value.indexOf("{{") > -1) return true;
|
|
1224
|
+
if (typeof value === "number") return isLength(value);
|
|
1225
|
+
if (typeof value !== "string") return false;
|
|
1226
|
+
if (isLength(value)) return true;
|
|
1227
|
+
return value.trim().split(/\s+/).every(isLength);
|
|
1228
|
+
}
|
|
1229
|
+
function checkAspectRatio(value) {
|
|
1230
|
+
if (typeof value === "string" && value.indexOf("{{") > -1) return true;
|
|
1231
|
+
if (typeof value === "number") return isFinite(value) && value > 0;
|
|
1232
|
+
if (typeof value !== "string") return false;
|
|
1233
|
+
return /^\s*\d+(\.\d+)?\s*(\/\s*\d+(\.\d+)?\s*)?$/.test(value);
|
|
1234
|
+
}
|
|
1235
|
+
function validateStage(stage, add, warn, ctx, prefix) {
|
|
1236
|
+
if (stage === void 0) {
|
|
1237
|
+
add("stage", "\u7F3A\u5C11 stage");
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
if (typeof stage !== "object" || stage === null) {
|
|
1241
|
+
add("stage", "stage \u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
if (!checkLength(stage.width)) {
|
|
1245
|
+
add(
|
|
1246
|
+
"stage.width",
|
|
1247
|
+
"stage.width \u5FC5\u987B\u662F\u6570\u5B57(px)\u6216\u957F\u5EA6\u5B57\u7B26\u4E32\uFF0C\u5982 460 / '100%' / 'calc(100% - 32px)'"
|
|
1248
|
+
);
|
|
1249
|
+
}
|
|
1250
|
+
if (!checkLength(stage.height)) {
|
|
1251
|
+
add("stage.height", "stage.height \u5FC5\u987B\u662F\u6570\u5B57(px)\u6216\u957F\u5EA6\u5B57\u7B26\u4E32\uFF0C\u5982 520 / 'auto'");
|
|
1252
|
+
}
|
|
1253
|
+
if (stage.layout !== void 0 && ["absolute", "flow"].indexOf(stage.layout) === -1) {
|
|
1254
|
+
add("stage.layout", "stage.layout \u53EA\u80FD\u662F 'absolute'\uFF08\u9ED8\u8BA4\uFF09\u6216 'flow'");
|
|
1255
|
+
}
|
|
1256
|
+
validateCloseButton(stage.closeButton, add, warn);
|
|
1257
|
+
const at = (key) => prefix ? `${prefix}.stage.${key}` : `stage.${key}`;
|
|
1258
|
+
if (stage.onShow !== void 0) validateAction(stage.onShow, at("onShow"), ctx);
|
|
1259
|
+
if (stage.onClose !== void 0) validateAction(stage.onClose, at("onClose"), ctx);
|
|
1260
|
+
if (stage.height === "auto" && stage.layout !== "flow") {
|
|
1261
|
+
add("stage.height", "height: 'auto' \u9700\u8981\u540C\u65F6\u8BBE\u7F6E stage.layout: 'flow'\uFF0C\u5426\u5219\u9AD8\u5EA6\u4F1A\u584C\u6210 0");
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
function validateCloseButton(config, add, warn) {
|
|
1265
|
+
if (config === void 0) return;
|
|
1266
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
1267
|
+
add("stage.closeButton", "closeButton \u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
if (config.position !== void 0 && CLOSE_POSITIONS.indexOf(config.position) === -1) {
|
|
1271
|
+
add("stage.closeButton.position", `position \u53EA\u80FD\u662F ${CLOSE_POSITIONS.join(" / ")}`);
|
|
1272
|
+
}
|
|
1273
|
+
if (config.offset !== void 0) {
|
|
1274
|
+
if (!Array.isArray(config.offset) || config.offset.length !== 2) {
|
|
1275
|
+
add("stage.closeButton.offset", "offset \u5FC5\u987B\u662F [x, y] \u4E24\u9879\u6570\u7EC4\uFF0C\u8D1F\u503C\u4F1A\u628A\u6309\u94AE\u79FB\u5230\u5F39\u7A97\u5916\u9762");
|
|
1276
|
+
} else {
|
|
1277
|
+
config.offset.forEach((value, index) => {
|
|
1278
|
+
if (!checkLength(value)) {
|
|
1279
|
+
add(`stage.closeButton.offset[${index}]`, `"${value}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6`);
|
|
1280
|
+
}
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (config.size !== void 0 && !checkLength(config.size)) {
|
|
1285
|
+
add("stage.closeButton.size", `"${config.size}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6`);
|
|
1286
|
+
}
|
|
1287
|
+
if (config.icon !== void 0 && typeof config.icon !== "string") {
|
|
1288
|
+
add("stage.closeButton.icon", "icon \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
1289
|
+
}
|
|
1290
|
+
if (config.image !== void 0 && typeof config.image !== "string") {
|
|
1291
|
+
add("stage.closeButton.image", "image \u5FC5\u987B\u662F\u56FE\u7247\u94FE\u63A5\u5B57\u7B26\u4E32");
|
|
1292
|
+
}
|
|
1293
|
+
if (config.style) {
|
|
1294
|
+
Object.keys(config.style).forEach((key) => {
|
|
1295
|
+
if (ALLOWED_STYLE_KEYS.indexOf(key) === -1) {
|
|
1296
|
+
warn(`stage.closeButton.style.${key}`, `\u6837\u5F0F\u5C5E\u6027 "${key}" \u4E0D\u5728\u767D\u540D\u5355\u5185\uFF0C\u6E32\u67D3\u65F6\u4F1A\u88AB\u5FFD\u7565`);
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
function validateData(data, add) {
|
|
1302
|
+
if (data === void 0) return [];
|
|
1303
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
1304
|
+
add("data", "data \u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
1305
|
+
return [];
|
|
1306
|
+
}
|
|
1307
|
+
Object.keys(data).forEach((key) => {
|
|
1308
|
+
const value = data[key];
|
|
1309
|
+
if (value && typeof value === "object" && !Array.isArray(value) && value.$source) {
|
|
1310
|
+
if (typeof value.$source !== "string") {
|
|
1311
|
+
add(`data.${key}.$source`, "$source \u5FC5\u987B\u662F\u6570\u636E\u6E90\u540D\u79F0\u5B57\u7B26\u4E32");
|
|
1312
|
+
}
|
|
1313
|
+
if (value.params !== void 0 && (typeof value.params !== "object" || value.params === null)) {
|
|
1314
|
+
add(`data.${key}.params`, "params \u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
});
|
|
1318
|
+
return Object.keys(data);
|
|
1319
|
+
}
|
|
1320
|
+
function validateState(state, add) {
|
|
1321
|
+
if (state === void 0) return [];
|
|
1322
|
+
if (typeof state !== "object" || state === null || Array.isArray(state)) {
|
|
1323
|
+
add("state", "state \u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
1324
|
+
return [];
|
|
1325
|
+
}
|
|
1326
|
+
return Object.keys(state);
|
|
1327
|
+
}
|
|
1328
|
+
function validateDerived(derived, dataKeys, stateKeys, add) {
|
|
1329
|
+
if (derived === void 0) return;
|
|
1330
|
+
if (typeof derived !== "object" || derived === null || Array.isArray(derived)) {
|
|
1331
|
+
add("derived", "derived \u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
Object.keys(derived).forEach((key) => {
|
|
1335
|
+
const item = derived[key] || {};
|
|
1336
|
+
if (typeof item.list !== "string") {
|
|
1337
|
+
add(`derived.${key}.list`, "list \u5FC5\u987B\u662F data \u4E2D\u7684\u6570\u7EC4\u5B57\u6BB5\u540D");
|
|
1338
|
+
} else if (dataKeys.indexOf(item.list) === -1) {
|
|
1339
|
+
add(`derived.${key}.list`, `data \u4E2D\u4E0D\u5B58\u5728 "${item.list}"`);
|
|
1340
|
+
}
|
|
1341
|
+
if (typeof item.indexBy !== "string") {
|
|
1342
|
+
add(`derived.${key}.indexBy`, "indexBy \u5FC5\u987B\u662F state \u4E2D\u7684\u5B57\u6BB5\u540D");
|
|
1343
|
+
} else if (stateKeys.indexOf(item.indexBy) === -1) {
|
|
1344
|
+
add(`derived.${key}.indexBy`, `state \u4E2D\u4E0D\u5B58\u5728 "${item.indexBy}"`);
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
function validateNode(node, path, layout, ctx) {
|
|
1349
|
+
const { add, warn } = ctx;
|
|
1350
|
+
if (!node || typeof node !== "object" || Array.isArray(node)) {
|
|
1351
|
+
add(path, "\u8282\u70B9\u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
if (NODE_TYPES.indexOf(node.type) === -1) {
|
|
1355
|
+
add(`${path}.type`, `\u672A\u77E5\u8282\u70B9\u7C7B\u578B "${node.type}"\uFF0C\u53EF\u7528\uFF1A${NODE_TYPES.join(" / ")}`);
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
if (layout === "absolute" && !Array.isArray(node.rect)) {
|
|
1359
|
+
add(`${path}.rect`, "\u5904\u4E8E\u7EDD\u5BF9\u5B9A\u4F4D\u5BB9\u5668\u4E2D\uFF0C\u5FC5\u987B\u63D0\u4F9B rect: [x, y, w, h]");
|
|
1360
|
+
}
|
|
1361
|
+
if (node.rect !== void 0) {
|
|
1362
|
+
if (!Array.isArray(node.rect)) {
|
|
1363
|
+
add(`${path}.rect`, "rect \u5FC5\u987B\u662F [x, y, w, h] \u6570\u7EC4");
|
|
1364
|
+
} else {
|
|
1365
|
+
node.rect.forEach((value, index) => {
|
|
1366
|
+
if (value !== void 0 && value !== null && !checkLength(value)) {
|
|
1367
|
+
add(`${path}.rect[${index}]`, `"${value}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6\uFF0C\u652F\u6301\u6570\u5B57(px) / '100%' / 'auto' \u7B49`);
|
|
1368
|
+
}
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
if (node.style) {
|
|
1373
|
+
Object.keys(node.style).forEach((key) => {
|
|
1374
|
+
if (ALLOWED_STYLE_KEYS.indexOf(key) === -1) {
|
|
1375
|
+
warn(`${path}.style.${key}`, `\u6837\u5F0F\u5C5E\u6027 "${key}" \u4E0D\u5728\u767D\u540D\u5355\u5185\uFF0C\u6E32\u67D3\u65F6\u4F1A\u88AB\u5FFD\u7565`);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
if (LENGTH_STYLE_KEYS.indexOf(key) > -1 && !checkLength(node.style[key])) {
|
|
1379
|
+
add(
|
|
1380
|
+
`${path}.style.${key}`,
|
|
1381
|
+
`"${node.style[key]}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6\uFF0C\u652F\u6301\u6570\u5B57(px) / '100%' / 'auto' \u7B49`
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
if (key === "aspectRatio" && !checkAspectRatio(node.style[key])) {
|
|
1385
|
+
add(`${path}.style.aspectRatio`, "aspectRatio \u8981\u5199\u6210 '750 / 200' \u6216 1.5 \u8FD9\u6837\u7684\u5BBD\u9AD8\u6BD4");
|
|
1386
|
+
}
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
if (node.visibleWhen !== void 0) {
|
|
1390
|
+
if (typeof node.visibleWhen !== "string") {
|
|
1391
|
+
add(`${path}.visibleWhen`, "visibleWhen \u5FC5\u987B\u662F\u8868\u8FBE\u5F0F\u5B57\u7B26\u4E32");
|
|
1392
|
+
} else {
|
|
1393
|
+
const error = check(node.visibleWhen);
|
|
1394
|
+
if (error) add(`${path}.visibleWhen`, `\u8868\u8FBE\u5F0F\u6709\u8BEF\uFF1A${error}`);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
if (node.action !== void 0) validateAction(node.action, `${path}.action`, ctx);
|
|
1398
|
+
validateNodeByType(node, path, ctx);
|
|
1399
|
+
}
|
|
1400
|
+
function validateNodeByType(node, path, ctx) {
|
|
1401
|
+
const { add } = ctx;
|
|
1402
|
+
if (node.type === "image" && !node.src) {
|
|
1403
|
+
add(`${path}.src`, "image \u5FC5\u987B\u63D0\u4F9B src");
|
|
1404
|
+
}
|
|
1405
|
+
if (node.type === "text" && node.content === void 0) {
|
|
1406
|
+
add(`${path}.content`, "text \u5FC5\u987B\u63D0\u4F9B content");
|
|
1407
|
+
}
|
|
1408
|
+
if (node.type === "button" && node.text === void 0) {
|
|
1409
|
+
add(`${path}.text`, "button \u5FC5\u987B\u63D0\u4F9B text");
|
|
1410
|
+
}
|
|
1411
|
+
if (node.type === "tabs") {
|
|
1412
|
+
if (typeof node.bind !== "string") {
|
|
1413
|
+
add(`${path}.bind`, "tabs \u5FC5\u987B\u63D0\u4F9B bind\uFF08data \u4E2D\u7684\u6570\u7EC4\u5B57\u6BB5\u540D\uFF09");
|
|
1414
|
+
}
|
|
1415
|
+
if (typeof node.stateKey !== "string") add(`${path}.stateKey`, "tabs \u5FC5\u987B\u63D0\u4F9B stateKey");
|
|
1416
|
+
if (node.labelField !== void 0 && typeof node.labelField !== "string") {
|
|
1417
|
+
add(`${path}.labelField`, "labelField \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
if (node.type === "countdown") {
|
|
1421
|
+
if (!node.to) add(`${path}.to`, "countdown \u5FC5\u987B\u63D0\u4F9B to\uFF08\u7ED3\u675F\u65F6\u95F4\uFF09");
|
|
1422
|
+
if (node.precision !== void 0 && ["s", "cs"].indexOf(node.precision) === -1) {
|
|
1423
|
+
add(`${path}.precision`, "precision \u53EA\u80FD\u662F 's'\uFF08\u6BCF\u79D2\uFF09\u6216 'cs'\uFF08\u5398\u79D2\uFF0C\u9010\u5E27\u5237\u65B0\uFF09");
|
|
1424
|
+
}
|
|
1425
|
+
if (node.children !== void 0 && !Array.isArray(node.children)) {
|
|
1426
|
+
add(`${path}.children`, "children \u5FC5\u987B\u662F\u6570\u7EC4");
|
|
1427
|
+
} else {
|
|
1428
|
+
(node.children || []).forEach((child, index) => {
|
|
1429
|
+
validateNode(child, `${path}.children[${index}]`, "flow", ctx);
|
|
1430
|
+
});
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
if (node.type === "repeat") {
|
|
1434
|
+
if (typeof node.bind !== "string") add(`${path}.bind`, "repeat \u5FC5\u987B\u63D0\u4F9B bind");
|
|
1435
|
+
if (!node.template) {
|
|
1436
|
+
add(`${path}.template`, "repeat \u5FC5\u987B\u63D0\u4F9B template");
|
|
1437
|
+
} else {
|
|
1438
|
+
validateNode(node.template, `${path}.template`, "flow", ctx);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
if (CONTAINER_TYPES.indexOf(node.type) > -1) {
|
|
1442
|
+
const childLayout = node.type === "box" ? "absolute" : "flow";
|
|
1443
|
+
if (node.children !== void 0 && !Array.isArray(node.children)) {
|
|
1444
|
+
add(`${path}.children`, "children \u5FC5\u987B\u662F\u6570\u7EC4");
|
|
1445
|
+
} else {
|
|
1446
|
+
(node.children || []).forEach((child, index) => {
|
|
1447
|
+
validateNode(child, `${path}.children[${index}]`, childLayout, ctx);
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
function validateAction(action, path, ctx) {
|
|
1453
|
+
const { add } = ctx;
|
|
1454
|
+
if (!action || typeof action !== "object" || Array.isArray(action)) {
|
|
1455
|
+
add(path, "action \u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
if (ACTION_TYPES.indexOf(action.type) === -1) {
|
|
1459
|
+
add(`${path}.type`, `\u672A\u77E5\u52A8\u4F5C "${action.type}"\uFF0C\u53EF\u7528\uFF1A${ACTION_TYPES.join(" / ")}`);
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
if (action.type === "navigate" && !action.url) {
|
|
1463
|
+
add(`${path}.url`, "navigate \u5FC5\u987B\u63D0\u4F9B url");
|
|
1464
|
+
}
|
|
1465
|
+
if (action.type === "setState" && typeof action.key !== "string") {
|
|
1466
|
+
add(`${path}.key`, "setState \u5FC5\u987B\u63D0\u4F9B key");
|
|
1467
|
+
}
|
|
1468
|
+
if (action.type === "track" && !action.event) {
|
|
1469
|
+
add(`${path}.event`, "track \u5FC5\u987B\u63D0\u4F9B event");
|
|
1470
|
+
}
|
|
1471
|
+
if (action.type === "call" && !action.name) {
|
|
1472
|
+
add(`${path}.name`, "call \u5FC5\u987B\u63D0\u4F9B name\uFF08\u5BBF\u4E3B\u6CE8\u518C\u7684\u65B9\u6CD5\u540D\uFF09");
|
|
1473
|
+
}
|
|
1474
|
+
if (action.type === "open") {
|
|
1475
|
+
if (!action.view) {
|
|
1476
|
+
add(`${path}.view`, "open \u5FC5\u987B\u63D0\u4F9B view\uFF08\u8981\u6253\u5F00\u7684\u89C6\u56FE\u540D\uFF09");
|
|
1477
|
+
} else if (ctx.viewNames && ctx.viewNames.indexOf(action.view) === -1) {
|
|
1478
|
+
add(`${path}.view`, `views \u91CC\u4E0D\u5B58\u5728\u89C6\u56FE "${action.view}"`);
|
|
1479
|
+
}
|
|
1480
|
+
if (action.mode !== void 0 && ["stack", "replace"].indexOf(action.mode) === -1) {
|
|
1481
|
+
add(`${path}.mode`, "mode \u53EA\u80FD\u662F 'stack'\uFF08\u9ED8\u8BA4\uFF0C\u53E0\u4E00\u5C42\uFF09\u6216 'replace'\uFF08\u6362\u6389\u5F53\u524D\u5C42\uFF09");
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
if (action.type === "sequence") {
|
|
1485
|
+
if (!Array.isArray(action.actions)) {
|
|
1486
|
+
add(`${path}.actions`, "sequence \u5FC5\u987B\u63D0\u4F9B actions \u6570\u7EC4");
|
|
1487
|
+
} else {
|
|
1488
|
+
action.actions.forEach(
|
|
1489
|
+
(item, index) => validateAction(item, `${path}.actions[${index}]`, ctx)
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
function formatIssues(issues) {
|
|
1495
|
+
return issues.map((item) => item.path ? `${item.path}: ${item.message}` : item.message).join("\n");
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
exports.ACTION_TYPES = ACTION_TYPES;
|
|
1499
|
+
exports.ALLOWED_STYLE_KEYS = ALLOWED_STYLE_KEYS;
|
|
1500
|
+
exports.CLOSE_POSITIONS = CLOSE_POSITIONS;
|
|
1501
|
+
exports.DSL_VERSION = DSL_VERSION;
|
|
1502
|
+
exports.NODE_TYPES = NODE_TYPES;
|
|
1503
|
+
exports.SINGLE_VIEW_NAME = SINGLE_VIEW_NAME;
|
|
1504
|
+
exports.check = check;
|
|
1505
|
+
exports.computeParts = computeParts;
|
|
1506
|
+
exports.createRuntime = createRuntime;
|
|
1507
|
+
exports.evaluate = evaluate;
|
|
1508
|
+
exports.formatIssues = formatIssues;
|
|
1509
|
+
exports.formatParts = formatParts;
|
|
1510
|
+
exports.interpolate = interpolate;
|
|
1511
|
+
exports.interpolateDeep = interpolateDeep;
|
|
1512
|
+
exports.isLength = isLength;
|
|
1513
|
+
exports.normalizeViews = normalizeViews;
|
|
1514
|
+
exports.parseEndTime = parseEndTime;
|
|
1515
|
+
exports.safeImageUrl = safeImageUrl;
|
|
1516
|
+
exports.safeUrl = safeUrl;
|
|
1517
|
+
exports.toCssStyle = toCssStyle;
|
|
1518
|
+
exports.toLength = toLength;
|
|
1519
|
+
exports.validate = validate;
|