@yaoxiu/marketing-dsl 1.2.0 → 1.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.
@@ -0,0 +1,968 @@
1
+ // src/expression.ts
2
+ var KEYWORD_LITERALS = {
3
+ true: true,
4
+ false: false,
5
+ null: null,
6
+ undefined: void 0
7
+ };
8
+ var PUNCTUATORS = [
9
+ "===",
10
+ "!==",
11
+ "<=",
12
+ ">=",
13
+ "==",
14
+ "!=",
15
+ "&&",
16
+ "||",
17
+ "(",
18
+ ")",
19
+ "[",
20
+ "]",
21
+ ".",
22
+ "?",
23
+ ":",
24
+ "<",
25
+ ">",
26
+ "!",
27
+ "+",
28
+ "-",
29
+ "*",
30
+ "/",
31
+ "%"
32
+ ];
33
+ function tokenize(input) {
34
+ const tokens = [];
35
+ let i = 0;
36
+ while (i < input.length) {
37
+ const ch = input[i];
38
+ if (/\s/.test(ch)) {
39
+ i++;
40
+ continue;
41
+ }
42
+ if (ch === '"' || ch === "'") {
43
+ const quote = ch;
44
+ let value = "";
45
+ i++;
46
+ while (i < input.length && input[i] !== quote) {
47
+ if (input[i] === "\\") {
48
+ value += input[i + 1] || "";
49
+ i += 2;
50
+ } else {
51
+ value += input[i];
52
+ i++;
53
+ }
54
+ }
55
+ if (i >= input.length) throw new Error("\u5B57\u7B26\u4E32\u6CA1\u6709\u95ED\u5408");
56
+ i++;
57
+ tokens.push({ type: "string", value });
58
+ continue;
59
+ }
60
+ if (/[0-9]/.test(ch) || ch === "." && /[0-9]/.test(input[i + 1] || "")) {
61
+ let raw = "";
62
+ while (i < input.length && /[0-9.]/.test(input[i])) {
63
+ raw += input[i];
64
+ i++;
65
+ }
66
+ tokens.push({ type: "number", value: Number(raw) });
67
+ continue;
68
+ }
69
+ if (/[A-Za-z_$]/.test(ch)) {
70
+ let name = "";
71
+ while (i < input.length && /[A-Za-z0-9_$]/.test(input[i])) {
72
+ name += input[i];
73
+ i++;
74
+ }
75
+ tokens.push({ type: "ident", value: name });
76
+ continue;
77
+ }
78
+ const punctuator = PUNCTUATORS.find((p) => input.startsWith(p, i));
79
+ if (!punctuator) throw new Error(`\u65E0\u6CD5\u8BC6\u522B\u7684\u5B57\u7B26 "${ch}"`);
80
+ tokens.push({ type: "punc", value: punctuator });
81
+ i += punctuator.length;
82
+ }
83
+ return tokens;
84
+ }
85
+ function createParser(tokens) {
86
+ let pos = 0;
87
+ const peek = () => tokens[pos];
88
+ const isPunc = (v) => {
89
+ const token = peek();
90
+ return !!token && token.type === "punc" && token.value === v;
91
+ };
92
+ const eat = (v) => {
93
+ if (!isPunc(v)) throw new Error(`\u7F3A\u5C11 "${v}"`);
94
+ pos++;
95
+ };
96
+ const binaryLevels = [
97
+ ["||"],
98
+ ["&&"],
99
+ ["===", "!==", "==", "!="],
100
+ ["<", ">", "<=", ">="],
101
+ ["+", "-"],
102
+ ["*", "/", "%"]
103
+ ];
104
+ function parseExpression() {
105
+ return parseTernary();
106
+ }
107
+ function parseTernary() {
108
+ const test = parseBinary(0);
109
+ if (!isPunc("?")) return test;
110
+ eat("?");
111
+ const consequent = parseTernary();
112
+ eat(":");
113
+ const alternate = parseTernary();
114
+ return { kind: "ternary", test, consequent, alternate };
115
+ }
116
+ function parseBinary(level) {
117
+ if (level >= binaryLevels.length) return parseUnary();
118
+ let left = parseBinary(level + 1);
119
+ for (; ; ) {
120
+ const token = peek();
121
+ if (!token || token.type !== "punc") break;
122
+ if (binaryLevels[level].indexOf(String(token.value)) === -1) break;
123
+ const operator = String(token.value);
124
+ pos++;
125
+ const right = parseBinary(level + 1);
126
+ left = { kind: "binary", operator, left, right };
127
+ }
128
+ return left;
129
+ }
130
+ function parseUnary() {
131
+ if (isPunc("!") || isPunc("-")) {
132
+ const operator = String(peek().value);
133
+ pos++;
134
+ return { kind: "unary", operator, argument: parseUnary() };
135
+ }
136
+ return parseMember();
137
+ }
138
+ function parseMember() {
139
+ let object = parsePrimary();
140
+ for (; ; ) {
141
+ if (isPunc(".")) {
142
+ eat(".");
143
+ const token = peek();
144
+ if (!token || token.type !== "ident") throw new Error('"." \u540E\u9762\u9700\u8981\u5C5E\u6027\u540D');
145
+ pos++;
146
+ object = {
147
+ kind: "member",
148
+ object,
149
+ property: { kind: "literal", value: token.value }
150
+ };
151
+ } else if (isPunc("[")) {
152
+ eat("[");
153
+ const property = parseExpression();
154
+ eat("]");
155
+ object = { kind: "member", object, property };
156
+ } else {
157
+ return object;
158
+ }
159
+ }
160
+ }
161
+ function parsePrimary() {
162
+ const token = peek();
163
+ if (!token) throw new Error("\u8868\u8FBE\u5F0F\u4E0D\u5B8C\u6574");
164
+ if (isPunc("(")) {
165
+ eat("(");
166
+ const node = parseExpression();
167
+ eat(")");
168
+ return node;
169
+ }
170
+ if (token.type === "number" || token.type === "string") {
171
+ pos++;
172
+ return { kind: "literal", value: token.value };
173
+ }
174
+ if (token.type === "ident") {
175
+ pos++;
176
+ const name = String(token.value);
177
+ if (Object.prototype.hasOwnProperty.call(KEYWORD_LITERALS, name)) {
178
+ return { kind: "literal", value: KEYWORD_LITERALS[name] };
179
+ }
180
+ return { kind: "identifier", name };
181
+ }
182
+ throw new Error(`\u610F\u5916\u7684\u7B26\u53F7 "${token.value}"`);
183
+ }
184
+ const ast = parseExpression();
185
+ if (pos < tokens.length) throw new Error(`\u8868\u8FBE\u5F0F\u7ED3\u5C3E\u6709\u591A\u4F59\u5185\u5BB9 "${tokens[pos].value}"`);
186
+ return ast;
187
+ }
188
+ var astCache = /* @__PURE__ */ new Map();
189
+ function parse(source) {
190
+ const cached = astCache.get(source);
191
+ if (cached) return cached;
192
+ const ast = createParser(tokenize(source));
193
+ astCache.set(source, ast);
194
+ return ast;
195
+ }
196
+ var BLOCKED_KEYS = ["__proto__", "constructor", "prototype"];
197
+ function readProperty(object, key) {
198
+ if (object === null || object === void 0) return void 0;
199
+ if (BLOCKED_KEYS.indexOf(String(key)) > -1) return void 0;
200
+ const value = object[String(key)];
201
+ return typeof value === "function" ? void 0 : value;
202
+ }
203
+ function evaluateNode(node, context) {
204
+ switch (node.kind) {
205
+ case "literal":
206
+ return node.value;
207
+ case "identifier":
208
+ return readProperty(context, node.name);
209
+ case "member":
210
+ return readProperty(
211
+ evaluateNode(node.object, context),
212
+ evaluateNode(node.property, context)
213
+ );
214
+ case "unary": {
215
+ const value = evaluateNode(node.argument, context);
216
+ return node.operator === "!" ? !value : -value;
217
+ }
218
+ case "ternary":
219
+ return evaluateNode(node.test, context) ? evaluateNode(node.consequent, context) : evaluateNode(node.alternate, context);
220
+ case "binary": {
221
+ if (node.operator === "&&") {
222
+ return evaluateNode(node.left, context) && evaluateNode(node.right, context);
223
+ }
224
+ if (node.operator === "||") {
225
+ return evaluateNode(node.left, context) || evaluateNode(node.right, context);
226
+ }
227
+ const left = evaluateNode(node.left, context);
228
+ const right = evaluateNode(node.right, context);
229
+ switch (node.operator) {
230
+ case "===":
231
+ return left === right;
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
+ default:
257
+ throw new Error(`\u4E0D\u652F\u6301\u7684\u8FD0\u7B97\u7B26 "${node.operator}"`);
258
+ }
259
+ }
260
+ default:
261
+ throw new Error("\u4E0D\u652F\u6301\u7684\u8282\u70B9\u7C7B\u578B");
262
+ }
263
+ }
264
+ function evaluate(source, context) {
265
+ try {
266
+ return evaluateNode(parse(source), context);
267
+ } catch (e) {
268
+ return void 0;
269
+ }
270
+ }
271
+ function check(source) {
272
+ try {
273
+ parse(source);
274
+ return "";
275
+ } catch (error) {
276
+ return error.message;
277
+ }
278
+ }
279
+ var INTERPOLATION_RE = /\{\{([\s\S]+?)\}\}/g;
280
+ function interpolate(input, context) {
281
+ if (typeof input !== "string" || input.indexOf("{{") === -1) return input;
282
+ const trimmed = input.trim();
283
+ const tokens = trimmed.match(INTERPOLATION_RE) || [];
284
+ if (tokens.length === 1 && tokens[0] === trimmed) {
285
+ return evaluate(trimmed.slice(2, -2).trim(), context);
286
+ }
287
+ return input.replace(INTERPOLATION_RE, (_, expr) => {
288
+ const value = evaluate(expr.trim(), context);
289
+ return value === void 0 || value === null ? "" : String(value);
290
+ });
291
+ }
292
+ function interpolateDeep(input, context) {
293
+ if (typeof input === "string") return interpolate(input, context);
294
+ if (Array.isArray(input)) {
295
+ return input.map((item) => interpolateDeep(item, context));
296
+ }
297
+ if (input && typeof input === "object") {
298
+ const result = {};
299
+ Object.keys(input).forEach((key) => {
300
+ result[key] = interpolateDeep(input[key], context);
301
+ });
302
+ return result;
303
+ }
304
+ return input;
305
+ }
306
+
307
+ // src/countdown.ts
308
+ function pad(value, len = 2) {
309
+ return String(value).padStart(len, "0");
310
+ }
311
+ function parseEndTime(to) {
312
+ if (typeof to === "number") return to;
313
+ if (to === void 0 || to === null || to === "") return 0;
314
+ const raw = String(to).trim();
315
+ if (!raw) return 0;
316
+ if (/^\d+$/.test(raw)) return Number(raw);
317
+ if (/^\d{4}年/.test(raw)) {
318
+ const normalizedZh = raw.replace(/年|月/g, "/").replace(/日/g, " ").replace(/时|点|分/g, ":").replace(/秒/g, "").replace(/:+/g, ":").replace(/\s+/g, " ").replace(/[:\s]+$/, "").replace(/(\s\d{1,2})$/, "$1:00").trim();
319
+ const time2 = new Date(normalizedZh).getTime();
320
+ return isNaN(time2) ? 0 : time2;
321
+ }
322
+ const isIso = /^\d{4}-\d{2}-\d{2}T/.test(raw);
323
+ const normalized = isIso ? raw : raw.replace(/-/g, "/");
324
+ const time = new Date(normalized).getTime();
325
+ return isNaN(time) ? 0 : time;
326
+ }
327
+ function isValidEndTime(to) {
328
+ if (typeof to === "number") return isFinite(to) && to > 0;
329
+ if (typeof to !== "string") return false;
330
+ if (to.indexOf("{{") > -1) return true;
331
+ return parseEndTime(to) > 0;
332
+ }
333
+ function computeParts(endTime, now = Date.now()) {
334
+ const raw = endTime - now;
335
+ const remain = raw > 0 ? raw : 0;
336
+ const cs = Math.floor(remain / 10);
337
+ const sec = Math.floor(cs / 100);
338
+ return {
339
+ d: String(Math.floor(sec / 86400)),
340
+ // h 是 0~23;跨天还想连着数小时的用 hAll(如 143:58:07)
341
+ h: pad(Math.floor(sec % 86400 / 3600)),
342
+ hAll: pad(Math.floor(sec / 3600)),
343
+ m: pad(Math.floor(sec % 3600 / 60)),
344
+ s: pad(sec % 60),
345
+ cs: pad(cs % 100),
346
+ ended: !endTime || remain <= 0
347
+ };
348
+ }
349
+ function formatParts(parts, format) {
350
+ 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);
351
+ }
352
+ function createTicker(options) {
353
+ let timer = null;
354
+ let raf = 0;
355
+ let running = false;
356
+ let precision = "s";
357
+ let endTimes = [];
358
+ const hasRaf = typeof requestAnimationFrame === "function";
359
+ function allEnded() {
360
+ const now = Date.now();
361
+ return endTimes.every((end) => !end || end - now <= 0);
362
+ }
363
+ function tick() {
364
+ options.onTick();
365
+ if (allEnded()) {
366
+ stop();
367
+ if (options.onAllEnded) options.onAllEnded();
368
+ }
369
+ }
370
+ function stop() {
371
+ running = false;
372
+ if (timer) {
373
+ clearInterval(timer);
374
+ timer = null;
375
+ }
376
+ if (raf) {
377
+ cancelAnimationFrame(raf);
378
+ raf = 0;
379
+ }
380
+ }
381
+ function sync(nextEndTimes, nextPrecision) {
382
+ const changed = nextPrecision !== precision || nextEndTimes.length !== endTimes.length || nextEndTimes.some((end, i) => end !== endTimes[i]);
383
+ endTimes = nextEndTimes;
384
+ precision = nextPrecision;
385
+ if (!changed && running) return;
386
+ stop();
387
+ if (!endTimes.length || allEnded()) return;
388
+ running = true;
389
+ if (precision === "cs" && hasRaf) {
390
+ const loop = () => {
391
+ tick();
392
+ if (running) raf = requestAnimationFrame(loop);
393
+ };
394
+ raf = requestAnimationFrame(loop);
395
+ } else {
396
+ timer = setInterval(tick, 1e3);
397
+ }
398
+ }
399
+ return { sync, stop };
400
+ }
401
+
402
+ // src/views.ts
403
+ var SINGLE_VIEW_NAME = "main";
404
+ function normalizeViews(dsl) {
405
+ if (!dsl || typeof dsl !== "object") {
406
+ return { entry: SINGLE_VIEW_NAME, views: {}, isMulti: false };
407
+ }
408
+ if (dsl.views && typeof dsl.views === "object" && !Array.isArray(dsl.views)) {
409
+ const names = Object.keys(dsl.views);
410
+ return {
411
+ entry: dsl.entry && dsl.views[dsl.entry] ? dsl.entry : names[0],
412
+ views: dsl.views,
413
+ isMulti: true
414
+ };
415
+ }
416
+ return {
417
+ entry: SINGLE_VIEW_NAME,
418
+ views: {
419
+ [SINGLE_VIEW_NAME]: {
420
+ type: dsl.type,
421
+ stage: dsl.stage,
422
+ nodes: dsl.nodes
423
+ }
424
+ },
425
+ isMulti: false
426
+ };
427
+ }
428
+
429
+ // src/style.ts
430
+ function toLength(value) {
431
+ return typeof value === "number" ? `${value}px` : String(value);
432
+ }
433
+ var px = toLength;
434
+ var LENGTH_RE = /^-?(\d+(\.\d+)?)(px|%|em|rem|vw|vh)$/;
435
+ function isLength(value) {
436
+ if (typeof value === "number") return isFinite(value);
437
+ if (typeof value !== "string") return false;
438
+ const trimmed = value.trim();
439
+ if (trimmed === "auto" || trimmed === "0") return true;
440
+ if (/^calc\([^;{}]*\)$/.test(trimmed)) return true;
441
+ return LENGTH_RE.test(trimmed);
442
+ }
443
+ var STYLE_MAP = {
444
+ // 文字
445
+ color: ["color", String],
446
+ fontSize: ["fontSize", px],
447
+ fontWeight: ["fontWeight", String],
448
+ fontFamily: ["fontFamily", String],
449
+ lineHeight: ["lineHeight", (v) => typeof v === "number" && v > 4 ? px(v) : String(v)],
450
+ letterSpacing: ["letterSpacing", px],
451
+ textAlign: ["textAlign", String],
452
+ textDecoration: ["textDecoration", String],
453
+ whiteSpace: ["whiteSpace", String],
454
+ // 倒计时这类逐帧变化的数字必须等宽,否则数字会左右抖动
455
+ fontVariantNumeric: ["fontVariantNumeric", String],
456
+ // 背景
457
+ background: ["background", String],
458
+ backgroundImage: ["backgroundImage", (v) => `url(${JSON.stringify(String(v))})`],
459
+ backgroundSize: ["backgroundSize", String],
460
+ backgroundPosition: ["backgroundPosition", String],
461
+ backgroundRepeat: ["backgroundRepeat", String],
462
+ // 边框与圆角
463
+ border: ["border", String],
464
+ borderColor: ["borderColor", String],
465
+ borderWidth: ["borderWidth", px],
466
+ borderStyle: ["borderStyle", String],
467
+ radius: ["borderRadius", px],
468
+ // 盒模型
469
+ padding: ["padding", px],
470
+ margin: ["margin", px],
471
+ marginTop: ["marginTop", px],
472
+ marginBottom: ["marginBottom", px],
473
+ marginLeft: ["marginLeft", px],
474
+ marginRight: ["marginRight", px],
475
+ overflow: ["overflow", String],
476
+ opacity: ["opacity", String],
477
+ shadow: ["boxShadow", String],
478
+ cursor: ["cursor", String],
479
+ // flex 容器
480
+ direction: ["flexDirection", (v) => v === "column" ? "column" : "row"],
481
+ gap: ["gap", px],
482
+ wrap: ["flexWrap", (v) => v ? "wrap" : "nowrap"],
483
+ align: ["alignItems", String],
484
+ justify: ["justifyContent", String],
485
+ // flex 子项
486
+ flex: ["flex", String],
487
+ // 不写时子项会被拉满父级交叉轴,标签这类「按内容宽度」的元素要显式声明
488
+ alignSelf: ["alignSelf", String],
489
+ width: ["width", px],
490
+ height: ["height", px],
491
+ minWidth: ["minWidth", px],
492
+ minHeight: ["minHeight", px],
493
+ maxWidth: ["maxWidth", px],
494
+ maxHeight: ["maxHeight", px],
495
+ // 图片
496
+ objectFit: ["objectFit", String],
497
+ // 宽度给百分比、高度按比例算。整图 banner 的热区必须靠它才能用百分比定位:
498
+ // 容器没有真实高度时,子节点的百分比 rect 算出来全是 0。
499
+ aspectRatio: ["aspectRatio", String]
500
+ };
501
+ var ALLOWED_STYLE_KEYS = Object.keys(STYLE_MAP);
502
+ function toCssStyle(style) {
503
+ const css = {};
504
+ if (!style) return css;
505
+ Object.keys(style).forEach((key) => {
506
+ const rule = STYLE_MAP[key];
507
+ if (!rule) return;
508
+ const value = style[key];
509
+ if (value === void 0 || value === null || value === "") return;
510
+ css[rule[0]] = rule[1](value);
511
+ });
512
+ return css;
513
+ }
514
+ function toRectStyle(rect) {
515
+ if (!Array.isArray(rect)) return {};
516
+ const [x, y, w, h] = rect;
517
+ const css = { position: "absolute", left: px(x || 0), top: px(y || 0) };
518
+ if (w !== void 0 && w !== null && w !== "auto") css.width = px(w);
519
+ if (h !== void 0 && h !== null && h !== "auto") css.height = px(h);
520
+ return css;
521
+ }
522
+ function resolveNodeStyle(node, layout) {
523
+ return Object.assign(
524
+ {},
525
+ layout === "absolute" ? toRectStyle(node.rect) : {},
526
+ toCssStyle(node.style)
527
+ );
528
+ }
529
+
530
+ // src/validate.ts
531
+ var LENGTH_STYLE_KEYS = [
532
+ "width",
533
+ "height",
534
+ "minWidth",
535
+ "minHeight",
536
+ "maxWidth",
537
+ "maxHeight",
538
+ "radius",
539
+ "padding",
540
+ "margin",
541
+ "marginTop",
542
+ "marginBottom",
543
+ "marginLeft",
544
+ "marginRight",
545
+ "gap",
546
+ "fontSize",
547
+ "letterSpacing",
548
+ "borderWidth"
549
+ ];
550
+ var DSL_VERSION = 1;
551
+ var NODE_TYPES = [
552
+ "box",
553
+ "flex",
554
+ "repeat",
555
+ "image",
556
+ "text",
557
+ "button",
558
+ "tabs",
559
+ "countdown"
560
+ ];
561
+ var ACTION_TYPES = [
562
+ "navigate",
563
+ "close",
564
+ "closeAll",
565
+ "open",
566
+ "setState",
567
+ "track",
568
+ "call",
569
+ "sequence"
570
+ ];
571
+ var TRACK_EVENT_PATTERN = /^[a-z][a-z0-9_]{2,63}$/;
572
+ var CLOSE_POSITIONS = [
573
+ "top-right",
574
+ "top-left",
575
+ "top-center",
576
+ "bottom-right",
577
+ "bottom-left",
578
+ "bottom-center"
579
+ ];
580
+ var CONTAINER_TYPES = ["box", "flex"];
581
+ function validate(dsl) {
582
+ const errors = [];
583
+ const warnings = [];
584
+ const add = (path, message) => errors.push({ path, message });
585
+ const warn = (path, message) => warnings.push({ path, message });
586
+ if (!dsl || typeof dsl !== "object" || Array.isArray(dsl)) {
587
+ return {
588
+ valid: false,
589
+ errors: [{ path: "", message: "\u6839\u8282\u70B9\u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61" }],
590
+ warnings
591
+ };
592
+ }
593
+ const doc = dsl;
594
+ if (doc.version !== DSL_VERSION) {
595
+ add(
596
+ "version",
597
+ `version \u5FC5\u987B\u662F ${DSL_VERSION}\uFF0C\u5F53\u524D\u4E3A ${JSON.stringify(doc.version)}`
598
+ );
599
+ }
600
+ const dataKeys = validateData(doc.data, add);
601
+ const stateKeys = validateState(doc.state, add);
602
+ validateDerived(doc.derived, dataKeys, stateKeys, add);
603
+ const isMulti = doc.views !== void 0;
604
+ if (isMulti) validateMultiView(doc, add);
605
+ const { views } = normalizeViews(doc);
606
+ const viewNames = Object.keys(views);
607
+ const ctx = { add, warn, viewNames };
608
+ viewNames.forEach((name) => {
609
+ validateView(views[name], isMulti ? `views.${name}` : "", ctx);
610
+ });
611
+ return { valid: errors.length === 0, errors, warnings };
612
+ }
613
+ function validateMultiView(dsl, add) {
614
+ if (typeof dsl.views !== "object" || dsl.views === null || Array.isArray(dsl.views)) {
615
+ add("views", 'views \u5FC5\u987B\u662F\u5BF9\u8C61\uFF0C\u5F62\u5982 { "main": { type, stage, nodes } }');
616
+ return;
617
+ }
618
+ if (!Object.keys(dsl.views).length) {
619
+ add("views", "views \u81F3\u5C11\u8981\u6709\u4E00\u4E2A\u89C6\u56FE");
620
+ return;
621
+ }
622
+ if (dsl.entry === void 0) {
623
+ add("entry", "\u591A\u89C6\u56FE\u5FC5\u987B\u6307\u5B9A entry\uFF08\u9996\u4E2A\u5C55\u793A\u7684\u89C6\u56FE\u540D\uFF09");
624
+ } else if (!dsl.views[dsl.entry]) {
625
+ add("entry", `entry "${dsl.entry}" \u5728 views \u91CC\u4E0D\u5B58\u5728`);
626
+ }
627
+ ["stage", "nodes"].forEach((key) => {
628
+ if (dsl[key] !== void 0) {
629
+ add(key, `\u7528\u4E86 views \u4E4B\u540E\uFF0C${key} \u8981\u5199\u5728\u6BCF\u4E2A\u89C6\u56FE\u91CC\uFF0C\u4E0D\u80FD\u7559\u5728\u9876\u5C42`);
630
+ }
631
+ });
632
+ }
633
+ function validateView(view, prefix, ctx) {
634
+ const { add, warn } = ctx;
635
+ const at = (key) => prefix ? `${prefix}.${key}` : key;
636
+ if (!view || typeof view !== "object" || Array.isArray(view)) {
637
+ add(prefix || "view", "\u89C6\u56FE\u5FC5\u987B\u662F\u5BF9\u8C61\uFF0C\u5305\u542B type / stage / nodes");
638
+ return;
639
+ }
640
+ if (["popup", "banner", "notice"].indexOf(view.type) === -1) {
641
+ add(at("type"), "type \u5FC5\u987B\u662F popup / banner / notice \u4E4B\u4E00");
642
+ }
643
+ validateStage(
644
+ view.stage,
645
+ (path, message) => add(prefix ? `${prefix}.${path}` : path, message),
646
+ warn,
647
+ ctx,
648
+ prefix
649
+ );
650
+ if (!Array.isArray(view.nodes) || view.nodes.length === 0) {
651
+ add(at("nodes"), "nodes \u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4");
652
+ return;
653
+ }
654
+ const rootLayout = (view.stage || {}).layout === "flow" ? "flow" : "absolute";
655
+ view.nodes.forEach((node, index) => {
656
+ validateNode(node, at(`nodes[${index}]`), rootLayout, ctx);
657
+ });
658
+ }
659
+ function checkLength(value) {
660
+ if (typeof value === "string" && value.indexOf("{{") > -1) return true;
661
+ if (typeof value === "number") return isLength(value);
662
+ if (typeof value !== "string") return false;
663
+ if (isLength(value)) return true;
664
+ return value.trim().split(/\s+/).every(isLength);
665
+ }
666
+ function checkAspectRatio(value) {
667
+ if (typeof value === "string" && value.indexOf("{{") > -1) return true;
668
+ if (typeof value === "number") return isFinite(value) && value > 0;
669
+ if (typeof value !== "string") return false;
670
+ return /^\s*\d+(\.\d+)?\s*(\/\s*\d+(\.\d+)?\s*)?$/.test(value);
671
+ }
672
+ function validateStage(stage, add, warn, ctx, prefix) {
673
+ if (stage === void 0) {
674
+ add("stage", "\u7F3A\u5C11 stage");
675
+ return;
676
+ }
677
+ if (typeof stage !== "object" || stage === null) {
678
+ add("stage", "stage \u5FC5\u987B\u662F\u5BF9\u8C61");
679
+ return;
680
+ }
681
+ if (!checkLength(stage.width)) {
682
+ add(
683
+ "stage.width",
684
+ "stage.width \u5FC5\u987B\u662F\u6570\u5B57(px)\u6216\u957F\u5EA6\u5B57\u7B26\u4E32\uFF0C\u5982 460 / '100%' / 'calc(100% - 32px)'"
685
+ );
686
+ }
687
+ if (!checkLength(stage.height)) {
688
+ add("stage.height", "stage.height \u5FC5\u987B\u662F\u6570\u5B57(px)\u6216\u957F\u5EA6\u5B57\u7B26\u4E32\uFF0C\u5982 520 / 'auto'");
689
+ }
690
+ if (stage.layout !== void 0 && ["absolute", "flow"].indexOf(stage.layout) === -1) {
691
+ add("stage.layout", "stage.layout \u53EA\u80FD\u662F 'absolute'\uFF08\u9ED8\u8BA4\uFF09\u6216 'flow'");
692
+ }
693
+ validateCloseButton(stage.closeButton, add, warn);
694
+ const at = (key) => prefix ? `${prefix}.stage.${key}` : `stage.${key}`;
695
+ if (stage.onShow !== void 0) validateAction(stage.onShow, at("onShow"), ctx);
696
+ if (stage.onClose !== void 0) validateAction(stage.onClose, at("onClose"), ctx);
697
+ if (stage.height === "auto" && stage.layout !== "flow") {
698
+ add(
699
+ "stage.height",
700
+ "height: 'auto' \u9700\u8981\u540C\u65F6\u8BBE\u7F6E stage.layout: 'flow'\uFF0C\u5426\u5219\u9AD8\u5EA6\u4F1A\u584C\u6210 0"
701
+ );
702
+ }
703
+ }
704
+ function validateCloseButton(config, add, warn) {
705
+ if (config === void 0) return;
706
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
707
+ add("stage.closeButton", "closeButton \u5FC5\u987B\u662F\u5BF9\u8C61");
708
+ return;
709
+ }
710
+ if (config.position !== void 0 && CLOSE_POSITIONS.indexOf(config.position) === -1) {
711
+ add("stage.closeButton.position", `position \u53EA\u80FD\u662F ${CLOSE_POSITIONS.join(" / ")}`);
712
+ }
713
+ if (config.offset !== void 0) {
714
+ if (!Array.isArray(config.offset) || config.offset.length !== 2) {
715
+ add(
716
+ "stage.closeButton.offset",
717
+ "offset \u5FC5\u987B\u662F [x, y] \u4E24\u9879\u6570\u7EC4\uFF0C\u8D1F\u503C\u4F1A\u628A\u6309\u94AE\u79FB\u5230\u5F39\u7A97\u5916\u9762"
718
+ );
719
+ } else {
720
+ config.offset.forEach((value, index) => {
721
+ if (!checkLength(value)) {
722
+ add(`stage.closeButton.offset[${index}]`, `"${value}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6`);
723
+ }
724
+ });
725
+ }
726
+ }
727
+ if (config.size !== void 0 && !checkLength(config.size)) {
728
+ add("stage.closeButton.size", `"${config.size}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6`);
729
+ }
730
+ if (config.icon !== void 0 && typeof config.icon !== "string") {
731
+ add("stage.closeButton.icon", "icon \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
732
+ }
733
+ if (config.image !== void 0 && typeof config.image !== "string") {
734
+ add("stage.closeButton.image", "image \u5FC5\u987B\u662F\u56FE\u7247\u94FE\u63A5\u5B57\u7B26\u4E32");
735
+ }
736
+ if (config.style) {
737
+ Object.keys(config.style).forEach((key) => {
738
+ if (ALLOWED_STYLE_KEYS.indexOf(key) === -1) {
739
+ warn(
740
+ `stage.closeButton.style.${key}`,
741
+ `\u6837\u5F0F\u5C5E\u6027 "${key}" \u4E0D\u5728\u767D\u540D\u5355\u5185\uFF0C\u6E32\u67D3\u65F6\u4F1A\u88AB\u5FFD\u7565`
742
+ );
743
+ }
744
+ });
745
+ }
746
+ }
747
+ function validateData(data, add) {
748
+ if (data === void 0) return [];
749
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
750
+ add("data", "data \u5FC5\u987B\u662F\u5BF9\u8C61");
751
+ return [];
752
+ }
753
+ Object.keys(data).forEach((key) => {
754
+ const value = data[key];
755
+ if (value && typeof value === "object" && !Array.isArray(value) && value.$source) {
756
+ if (typeof value.$source !== "string") {
757
+ add(`data.${key}.$source`, "$source \u5FC5\u987B\u662F\u6570\u636E\u6E90\u540D\u79F0\u5B57\u7B26\u4E32");
758
+ }
759
+ if (value.params !== void 0 && (typeof value.params !== "object" || value.params === null)) {
760
+ add(`data.${key}.params`, "params \u5FC5\u987B\u662F\u5BF9\u8C61");
761
+ }
762
+ }
763
+ });
764
+ return Object.keys(data);
765
+ }
766
+ function validateState(state, add) {
767
+ if (state === void 0) return [];
768
+ if (typeof state !== "object" || state === null || Array.isArray(state)) {
769
+ add("state", "state \u5FC5\u987B\u662F\u5BF9\u8C61");
770
+ return [];
771
+ }
772
+ return Object.keys(state);
773
+ }
774
+ function validateDerived(derived, dataKeys, stateKeys, add) {
775
+ if (derived === void 0) return;
776
+ if (typeof derived !== "object" || derived === null || Array.isArray(derived)) {
777
+ add("derived", "derived \u5FC5\u987B\u662F\u5BF9\u8C61");
778
+ return;
779
+ }
780
+ Object.keys(derived).forEach((key) => {
781
+ const item = derived[key] || {};
782
+ if (typeof item.list !== "string") {
783
+ add(`derived.${key}.list`, "list \u5FC5\u987B\u662F data \u4E2D\u7684\u6570\u7EC4\u5B57\u6BB5\u540D");
784
+ } else if (dataKeys.indexOf(item.list) === -1) {
785
+ add(`derived.${key}.list`, `data \u4E2D\u4E0D\u5B58\u5728 "${item.list}"`);
786
+ }
787
+ if (typeof item.indexBy !== "string") {
788
+ add(`derived.${key}.indexBy`, "indexBy \u5FC5\u987B\u662F state \u4E2D\u7684\u5B57\u6BB5\u540D");
789
+ } else if (stateKeys.indexOf(item.indexBy) === -1) {
790
+ add(`derived.${key}.indexBy`, `state \u4E2D\u4E0D\u5B58\u5728 "${item.indexBy}"`);
791
+ }
792
+ });
793
+ }
794
+ function validateNode(node, path, layout, ctx) {
795
+ const { add, warn } = ctx;
796
+ if (!node || typeof node !== "object" || Array.isArray(node)) {
797
+ add(path, "\u8282\u70B9\u5FC5\u987B\u662F\u5BF9\u8C61");
798
+ return;
799
+ }
800
+ if (NODE_TYPES.indexOf(node.type) === -1) {
801
+ add(`${path}.type`, `\u672A\u77E5\u8282\u70B9\u7C7B\u578B "${node.type}"\uFF0C\u53EF\u7528\uFF1A${NODE_TYPES.join(" / ")}`);
802
+ return;
803
+ }
804
+ if (layout === "absolute" && !Array.isArray(node.rect)) {
805
+ add(`${path}.rect`, "\u5904\u4E8E\u7EDD\u5BF9\u5B9A\u4F4D\u5BB9\u5668\u4E2D\uFF0C\u5FC5\u987B\u63D0\u4F9B rect: [x, y, w, h]");
806
+ }
807
+ if (node.rect !== void 0) {
808
+ if (!Array.isArray(node.rect)) {
809
+ add(`${path}.rect`, "rect \u5FC5\u987B\u662F [x, y, w, h] \u6570\u7EC4");
810
+ } else {
811
+ node.rect.forEach((value, index) => {
812
+ if (value !== void 0 && value !== null && !checkLength(value)) {
813
+ add(
814
+ `${path}.rect[${index}]`,
815
+ `"${value}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6\uFF0C\u652F\u6301\u6570\u5B57(px) / '100%' / 'auto' \u7B49`
816
+ );
817
+ }
818
+ });
819
+ }
820
+ }
821
+ if (node.style) {
822
+ Object.keys(node.style).forEach((key) => {
823
+ if (ALLOWED_STYLE_KEYS.indexOf(key) === -1) {
824
+ warn(`${path}.style.${key}`, `\u6837\u5F0F\u5C5E\u6027 "${key}" \u4E0D\u5728\u767D\u540D\u5355\u5185\uFF0C\u6E32\u67D3\u65F6\u4F1A\u88AB\u5FFD\u7565`);
825
+ return;
826
+ }
827
+ if (LENGTH_STYLE_KEYS.indexOf(key) > -1 && !checkLength(node.style[key])) {
828
+ add(
829
+ `${path}.style.${key}`,
830
+ `"${node.style[key]}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6\uFF0C\u652F\u6301\u6570\u5B57(px) / '100%' / 'auto' \u7B49`
831
+ );
832
+ }
833
+ if (key === "aspectRatio" && !checkAspectRatio(node.style[key])) {
834
+ add(
835
+ `${path}.style.aspectRatio`,
836
+ "aspectRatio \u8981\u5199\u6210 '750 / 200' \u6216 1.5 \u8FD9\u6837\u7684\u5BBD\u9AD8\u6BD4"
837
+ );
838
+ }
839
+ });
840
+ }
841
+ if (node.visibleWhen !== void 0) {
842
+ if (typeof node.visibleWhen !== "string") {
843
+ add(`${path}.visibleWhen`, "visibleWhen \u5FC5\u987B\u662F\u8868\u8FBE\u5F0F\u5B57\u7B26\u4E32");
844
+ } else {
845
+ const error = check(node.visibleWhen);
846
+ if (error) add(`${path}.visibleWhen`, `\u8868\u8FBE\u5F0F\u6709\u8BEF\uFF1A${error}`);
847
+ }
848
+ }
849
+ if (node.action !== void 0) validateAction(node.action, `${path}.action`, ctx);
850
+ validateNodeByType(node, path, ctx);
851
+ }
852
+ function validateNodeByType(node, path, ctx) {
853
+ const { add } = ctx;
854
+ if (node.type === "image" && !node.src) {
855
+ add(`${path}.src`, "image \u5FC5\u987B\u63D0\u4F9B src");
856
+ }
857
+ if (node.type === "text" && node.content === void 0) {
858
+ add(`${path}.content`, "text \u5FC5\u987B\u63D0\u4F9B content");
859
+ }
860
+ if (node.type === "button" && node.text === void 0) {
861
+ add(`${path}.text`, "button \u5FC5\u987B\u63D0\u4F9B text");
862
+ }
863
+ if (node.type === "tabs") {
864
+ if (typeof node.bind !== "string") {
865
+ add(`${path}.bind`, "tabs \u5FC5\u987B\u63D0\u4F9B bind\uFF08data \u4E2D\u7684\u6570\u7EC4\u5B57\u6BB5\u540D\uFF09");
866
+ }
867
+ if (typeof node.stateKey !== "string")
868
+ add(`${path}.stateKey`, "tabs \u5FC5\u987B\u63D0\u4F9B stateKey");
869
+ if (node.labelField !== void 0 && typeof node.labelField !== "string") {
870
+ add(`${path}.labelField`, "labelField \u5FC5\u987B\u662F\u5B57\u7B26\u4E32");
871
+ }
872
+ }
873
+ if (node.type === "countdown") {
874
+ if (!node.to) {
875
+ add(`${path}.to`, "countdown \u5FC5\u987B\u63D0\u4F9B to\uFF08\u7ED3\u675F\u65F6\u95F4\uFF09");
876
+ } else if (!isValidEndTime(node.to)) {
877
+ add(
878
+ `${path}.to`,
879
+ `"${node.to}" \u4E0D\u662F\u80FD\u8BC6\u522B\u7684\u65F6\u95F4\u3002\u53EF\u4EE5\u5199 "2026-09-01 18:00:00" / "2026-09-01" / "2026\u5E749\u67081\u65E5 18\u70B9" / ISO \u683C\u5F0F / \u65F6\u95F4\u6233`
880
+ );
881
+ }
882
+ if (node.precision !== void 0 && ["s", "cs"].indexOf(node.precision) === -1) {
883
+ add(`${path}.precision`, "precision \u53EA\u80FD\u662F 's'\uFF08\u6BCF\u79D2\uFF09\u6216 'cs'\uFF08\u5398\u79D2\uFF0C\u9010\u5E27\u5237\u65B0\uFF09");
884
+ }
885
+ if (node.children !== void 0 && !Array.isArray(node.children)) {
886
+ add(`${path}.children`, "children \u5FC5\u987B\u662F\u6570\u7EC4");
887
+ } else {
888
+ (node.children || []).forEach((child, index) => {
889
+ validateNode(child, `${path}.children[${index}]`, "flow", ctx);
890
+ });
891
+ }
892
+ }
893
+ if (node.type === "repeat") {
894
+ if (typeof node.bind !== "string") add(`${path}.bind`, "repeat \u5FC5\u987B\u63D0\u4F9B bind");
895
+ if (!node.template) {
896
+ add(`${path}.template`, "repeat \u5FC5\u987B\u63D0\u4F9B template");
897
+ } else {
898
+ validateNode(node.template, `${path}.template`, "flow", ctx);
899
+ }
900
+ }
901
+ if (CONTAINER_TYPES.indexOf(node.type) > -1) {
902
+ const childLayout = node.type === "box" ? "absolute" : "flow";
903
+ if (node.children !== void 0 && !Array.isArray(node.children)) {
904
+ add(`${path}.children`, "children \u5FC5\u987B\u662F\u6570\u7EC4");
905
+ } else {
906
+ (node.children || []).forEach((child, index) => {
907
+ validateNode(child, `${path}.children[${index}]`, childLayout, ctx);
908
+ });
909
+ }
910
+ }
911
+ }
912
+ function validateAction(action, path, ctx) {
913
+ const { add } = ctx;
914
+ if (!action || typeof action !== "object" || Array.isArray(action)) {
915
+ add(path, "action \u5FC5\u987B\u662F\u5BF9\u8C61");
916
+ return;
917
+ }
918
+ if (ACTION_TYPES.indexOf(action.type) === -1) {
919
+ add(`${path}.type`, `\u672A\u77E5\u52A8\u4F5C "${action.type}"\uFF0C\u53EF\u7528\uFF1A${ACTION_TYPES.join(" / ")}`);
920
+ return;
921
+ }
922
+ if (action.type === "navigate" && !action.url) {
923
+ add(`${path}.url`, "navigate \u5FC5\u987B\u63D0\u4F9B url");
924
+ }
925
+ if (action.type === "setState" && typeof action.key !== "string") {
926
+ add(`${path}.key`, "setState \u5FC5\u987B\u63D0\u4F9B key");
927
+ }
928
+ if (action.type === "track") {
929
+ if (!action.event) {
930
+ add(`${path}.event`, "track \u5FC5\u987B\u63D0\u4F9B event");
931
+ } else if (typeof action.event !== "string" || !TRACK_EVENT_PATTERN.test(action.event)) {
932
+ add(
933
+ `${path}.event`,
934
+ `\u57CB\u70B9\u4E8B\u4EF6\u540D "${String(action.event)}" \u4E0D\u5408\u89C4\uFF1A\u53EA\u80FD\u7528\u5C0F\u5199\u82F1\u6587\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u4E0B\u5212\u7EBF\uFF0C\u5FC5\u987B\u4EE5\u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u603B\u957F\u5EA6 3~64 \u4E2A\u5B57\u7B26\uFF08\u4E0D\u80FD\u7528\u4E2D\u6587\u3001\u5927\u5199\u5B57\u6BCD\u3001\u9A7C\u5CF0\u3001\u4E2D\u5212\u7EBF\u6216\u7A7A\u683C\uFF09\u3002\u6B63\u786E\u793A\u4F8B\uFF1Abanner_renew_click`
935
+ );
936
+ }
937
+ }
938
+ if (action.type === "call" && !action.name) {
939
+ add(`${path}.name`, "call \u5FC5\u987B\u63D0\u4F9B name\uFF08\u5BBF\u4E3B\u6CE8\u518C\u7684\u65B9\u6CD5\u540D\uFF09");
940
+ }
941
+ if (action.type === "open") {
942
+ if (!action.view) {
943
+ add(`${path}.view`, "open \u5FC5\u987B\u63D0\u4F9B view\uFF08\u8981\u6253\u5F00\u7684\u89C6\u56FE\u540D\uFF09");
944
+ } else if (ctx.viewNames && ctx.viewNames.indexOf(action.view) === -1) {
945
+ add(`${path}.view`, `views \u91CC\u4E0D\u5B58\u5728\u89C6\u56FE "${action.view}"`);
946
+ }
947
+ if (action.mode !== void 0 && ["stack", "replace"].indexOf(action.mode) === -1) {
948
+ add(
949
+ `${path}.mode`,
950
+ "mode \u53EA\u80FD\u662F 'stack'\uFF08\u9ED8\u8BA4\uFF0C\u53E0\u4E00\u5C42\uFF09\u6216 'replace'\uFF08\u6362\u6389\u5F53\u524D\u5C42\uFF09"
951
+ );
952
+ }
953
+ }
954
+ if (action.type === "sequence") {
955
+ if (!Array.isArray(action.actions)) {
956
+ add(`${path}.actions`, "sequence \u5FC5\u987B\u63D0\u4F9B actions \u6570\u7EC4");
957
+ } else {
958
+ action.actions.forEach(
959
+ (item, index) => validateAction(item, `${path}.actions[${index}]`, ctx)
960
+ );
961
+ }
962
+ }
963
+ }
964
+ function formatIssues(issues) {
965
+ return issues.map((item) => item.path ? `${item.path}: ${item.message}` : item.message).join("\n");
966
+ }
967
+
968
+ export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, DSL_VERSION, NODE_TYPES, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, check, computeParts, createTicker, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, resolveNodeStyle, toCssStyle, toLength, validate };