@yaoxiu/marketing-dsl 2.8.0 → 2.9.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 CHANGED
@@ -111,7 +111,7 @@ const unsubscribe = runtime.subscribe(() => {
111
111
  // 还会把用户已经切到的 tab 打回默认值。
112
112
  runtime.update({ user: { shopName: '麦爆了旗舰店' } });
113
113
 
114
- // 卸载时必须调,否则倒计时定时器不会停
114
+ // 卸载时必须调,否则倒计时 / 自动轮播的定时器不会停
115
115
  runtime.destroy();
116
116
  unsubscribe();
117
117
  ```
@@ -143,6 +143,8 @@ unsubscribe();
143
143
  countdownPrecision: 's',
144
144
  css: '', // 要注入的 CSS 文本(keyframes + hover 规则),没有动画时是空串
145
145
  rootClassName: 'dsl-renderer dsl-r3', // 根容器类名,后半截是这份物料的作用域前缀
146
+ onRootEnter: fn, // 可选:只有配了 pauseOnHover 的自动轮播才有,见下
147
+ onRootLeave: fn,
146
148
  }
147
149
  ```
148
150
 
@@ -167,6 +169,28 @@ unsubscribe();
167
169
 
168
170
  父子都可点是常见排版——公告条整条点开弹窗、行末 × 关闭公告条——不阻止的话点 × 会连带触发外层,关掉的瞬间又弹出来。没有 `onClick` 的节点不要绑任何事件,让它正常冒泡到有 `onClick` 的祖先。
169
171
 
172
+ ### 自动轮播(`derived.xxx.autoplay`)
173
+
174
+ ```jsonc
175
+ "state": { "slide": 0 },
176
+ "derived": { "cur": { "list": "slides", "indexBy": "slide",
177
+ "autoplay": { "interval": 4000, "pauseOnHover": true } } }
178
+ ```
179
+
180
+ 配了就开启、始终循环(没有 `enabled` / `loop`):每 `interval` 毫秒把 `state[indexBy]` 推到
181
+ `(当前 + 1) % 列表长度`。定时逻辑在 `src/autoplay.ts`,`runtime.ts` 只接线。
182
+
183
+ - **与点击同一条路径**:到点调的就是运行时的 `setState`,`state-change` 照常派发,文案 / 图片 /
184
+ tabs 指示器 / 链接 / 运营配的 `transition` 与手动点击效果完全一致,不引入额外切换动画
185
+ - 任何 `setState` 改了同一个 key(用户点 tab)都会**重新计时**
186
+ - 列表长度 ≤1、数据源未就绪或失败时不播;数据源回来后开始;`editMode` 下照样播
187
+ - `destroy()` 清掉全部轮播定时器,之后不会再 `setState`
188
+ - `pauseOnHover` 需要壳配合:渲染树此时带 `onRootEnter` / `onRootLeave`,
189
+ 壳在根容器上绑 `mouseenter` / `mouseleave` 原样调用即可(**有才绑、不拦冒泡、不加判断**)
190
+
191
+ 校验:`autoplay` 须是对象;`interval` 须是 ≥300 的数字(单位毫秒,否则 error);
192
+ `pauseOnHover` 须是布尔;其余字段只告警(运行时忽略)。字段表导出为 `AUTOPLAY_FIELDS`。
193
+
170
194
  ## 运行时事件(`emit`)
171
195
 
172
196
  | 事件 | 何时抛 | 宿主拿它干嘛 |
@@ -399,6 +399,109 @@ function createTicker(options) {
399
399
  return { sync, stop };
400
400
  }
401
401
 
402
+ // src/autoplay.ts
403
+ var DERIVED_FIELDS = ["list", "indexBy", "autoplay"];
404
+ var AUTOPLAY_FIELDS = ["interval", "pauseOnHover"];
405
+ var AUTOPLAY_MIN_INTERVAL = 300;
406
+ function collectEntries(derived) {
407
+ const entries = [];
408
+ Object.keys(derived || {}).forEach((name) => {
409
+ const config = (derived || {})[name];
410
+ const autoplay = config && config.autoplay;
411
+ if (!autoplay || typeof autoplay !== "object") return;
412
+ const interval = Number(autoplay.interval);
413
+ if (!isFinite(interval) || interval < AUTOPLAY_MIN_INTERVAL) return;
414
+ entries.push({
415
+ list: config.list,
416
+ indexBy: config.indexBy,
417
+ interval,
418
+ pauseOnHover: autoplay.pauseOnHover === true,
419
+ timer: null
420
+ });
421
+ });
422
+ return entries;
423
+ }
424
+ function createAutoplay(options) {
425
+ const entries = collectEntries(options.derived);
426
+ let started = false;
427
+ let hovering = false;
428
+ let stopped = false;
429
+ function clear(entry) {
430
+ if (entry.timer) {
431
+ clearTimeout(entry.timer);
432
+ entry.timer = null;
433
+ }
434
+ }
435
+ function advance(entry) {
436
+ entry.timer = null;
437
+ const length = options.getLength(entry.list);
438
+ if (length <= 1) return;
439
+ const index = options.getIndex(entry.indexBy);
440
+ options.setState(entry.indexBy, (index + 1) % length);
441
+ }
442
+ function schedule(entry) {
443
+ clear(entry);
444
+ if (stopped || !started) return;
445
+ if (hovering && entry.pauseOnHover) return;
446
+ if (options.getLength(entry.list) <= 1) return;
447
+ entry.timer = setTimeout(() => advance(entry), entry.interval);
448
+ }
449
+ return {
450
+ pausable: entries.some((entry) => entry.pauseOnHover),
451
+ start() {
452
+ started = true;
453
+ entries.forEach(schedule);
454
+ },
455
+ reset(key) {
456
+ entries.forEach((entry) => {
457
+ if (entry.indexBy === key) schedule(entry);
458
+ });
459
+ },
460
+ pause() {
461
+ hovering = true;
462
+ entries.forEach((entry) => {
463
+ if (entry.pauseOnHover) clear(entry);
464
+ });
465
+ },
466
+ resume() {
467
+ hovering = false;
468
+ entries.forEach((entry) => {
469
+ if (entry.pauseOnHover) schedule(entry);
470
+ });
471
+ },
472
+ stop() {
473
+ stopped = true;
474
+ entries.forEach(clear);
475
+ }
476
+ };
477
+ }
478
+ function validateAutoplay(autoplay, path, add, warn) {
479
+ if (autoplay === void 0) return;
480
+ if (!autoplay || typeof autoplay !== "object" || Array.isArray(autoplay)) {
481
+ add(path, 'autoplay \u5FC5\u987B\u662F\u5BF9\u8C61\uFF0C\u5F62\u5982 { "interval": 4000, "pauseOnHover": true }');
482
+ return;
483
+ }
484
+ const config = autoplay;
485
+ const interval = config.interval;
486
+ if (typeof interval !== "number" || !isFinite(interval) || interval < AUTOPLAY_MIN_INTERVAL) {
487
+ add(
488
+ `${path}.interval`,
489
+ `interval \u5FC5\u987B\u662F\u4E0D\u5C0F\u4E8E ${AUTOPLAY_MIN_INTERVAL} \u7684\u6570\u5B57\uFF0C\u5355\u4F4D\u6BEB\u79D2\uFF08\u5982 4000 \u8868\u793A 4 \u79D2\uFF09\uFF0C\u5F53\u524D\u4E3A ${JSON.stringify(interval)}`
490
+ );
491
+ }
492
+ if (config.pauseOnHover !== void 0 && typeof config.pauseOnHover !== "boolean") {
493
+ add(`${path}.pauseOnHover`, "pauseOnHover \u5FC5\u987B\u662F true \u6216 false");
494
+ }
495
+ Object.keys(config).forEach((key) => {
496
+ if (AUTOPLAY_FIELDS.indexOf(key) === -1) {
497
+ warn(
498
+ `${path}.${key}`,
499
+ `autoplay \u4E0D\u8BA4\u8BC6 "${key}"\uFF0C\u4F1A\u88AB\u5FFD\u7565\u3002\u53EF\u7528\uFF1A${AUTOPLAY_FIELDS.join(" / ")}\uFF08\u914D\u4E86 autoplay \u5C31\u5F00\u542F\u4E14\u59CB\u7EC8\u5FAA\u73AF\uFF0C\u4E0D\u9700\u8981 enabled / loop\uFF09`
500
+ );
501
+ }
502
+ });
503
+ }
504
+
402
505
  // src/views.ts
403
506
  var SINGLE_VIEW_NAME = "main";
404
507
  function normalizeViews(dsl) {
@@ -962,7 +1065,7 @@ function validate(dsl, options) {
962
1065
  }
963
1066
  const dataKeys = validateData(doc.data, add);
964
1067
  const stateKeys = validateState(doc.state, add);
965
- validateDerived(doc.derived, dataKeys, stateKeys, add);
1068
+ validateDerived(doc.derived, dataKeys, stateKeys, add, warn);
966
1069
  const keyframeNames = validateKeyframes(doc.keyframes, add, warn);
967
1070
  const isMulti = doc.views !== void 0;
968
1071
  if (isMulti) validateMultiView(doc, add);
@@ -1211,7 +1314,7 @@ function validateState(state, add) {
1211
1314
  }
1212
1315
  return Object.keys(state);
1213
1316
  }
1214
- function validateDerived(derived, dataKeys, stateKeys, add) {
1317
+ function validateDerived(derived, dataKeys, stateKeys, add, warn) {
1215
1318
  if (derived === void 0) return;
1216
1319
  if (typeof derived !== "object" || derived === null || Array.isArray(derived)) {
1217
1320
  add("derived", "derived \u5FC5\u987B\u662F\u5BF9\u8C61");
@@ -1229,6 +1332,7 @@ function validateDerived(derived, dataKeys, stateKeys, add) {
1229
1332
  } else if (stateKeys.indexOf(item.indexBy) === -1) {
1230
1333
  add(`derived.${key}.indexBy`, `state \u4E2D\u4E0D\u5B58\u5728 "${item.indexBy}"`);
1231
1334
  }
1335
+ validateAutoplay(item.autoplay, `derived.${key}.autoplay`, add, warn);
1232
1336
  });
1233
1337
  }
1234
1338
  function validateNodeStyle(style, path, ctx) {
@@ -1443,4 +1547,4 @@ function formatIssues(issues) {
1443
1547
  return issues.map((item) => item.path ? `${item.path}: ${item.message}` : item.message).join("\n");
1444
1548
  }
1445
1549
 
1446
- export { ACTION_TYPES, ALLOWED_EASINGS, ALLOWED_STYLE_KEYS, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, CSS_NAME_PATTERN, DEFAULT_USER_FIELDS, DSL_VERSION, NODE_TYPES, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, check, computeParts, createCssBuilder, createTicker, declarations, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isKeyframeOffset, isLength, isValidEasing, isValidEndTime, nextScopeUid, normalizeViews, parseEndTime, resolveNodeStyle, sanitizeCssValue, toCssStyle, toLength, validate, validateUserFields };
1550
+ export { ACTION_TYPES, ALLOWED_EASINGS, ALLOWED_STYLE_KEYS, AUTOPLAY_FIELDS, AUTOPLAY_MIN_INTERVAL, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, CSS_NAME_PATTERN, DEFAULT_USER_FIELDS, DERIVED_FIELDS, DSL_VERSION, NODE_TYPES, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, check, computeParts, createAutoplay, createCssBuilder, createTicker, declarations, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isKeyframeOffset, isLength, isValidEasing, isValidEndTime, nextScopeUid, normalizeViews, parseEndTime, resolveNodeStyle, sanitizeCssValue, toCssStyle, toLength, validate, validateAutoplay, validateUserFields };
@@ -129,6 +129,11 @@ var ALLOWED_EASINGS = [
129
129
  "step-end"
130
130
  ];
131
131
 
132
+ // src/autoplay.ts
133
+ var DERIVED_FIELDS = ["list", "indexBy", "autoplay"];
134
+ var AUTOPLAY_FIELDS = ["interval", "pauseOnHover"];
135
+ var AUTOPLAY_MIN_INTERVAL = 300;
136
+
132
137
  // src/validate.ts
133
138
  var DSL_VERSION = 1;
134
139
  var NODE_TYPES = [
@@ -862,6 +867,18 @@ var userFieldRows = DEFAULT_USER_FIELDS.map((field) => [
862
867
  `\`user.${field}\``,
863
868
  USER_FIELD_LABELS[field] || ""
864
869
  ]);
870
+ var derivedRows = [
871
+ ["list", "`data` \u91CC\u7684\u6570\u7EC4\u5B57\u6BB5\u540D"],
872
+ ["indexBy", "`state` \u91CC\u7684\u4E0B\u6807\u5B57\u6BB5\u540D"],
873
+ ["autoplay", "\u53EF\u9009\u3002\u81EA\u52A8\u8F6E\u64AD\uFF0C\u89C1\u4E0B"]
874
+ ];
875
+ var autoplayRows = [
876
+ [
877
+ "interval",
878
+ `\u5FC5\u586B\u3002\u5207\u6362\u95F4\u9694\uFF0C**\u5355\u4F4D\u6BEB\u79D2**\uFF0C\u4E0D\u5C0F\u4E8E ${AUTOPLAY_MIN_INTERVAL}\uFF08\u5982 \`4000\` = 4 \u79D2\uFF09`
879
+ ],
880
+ ["pauseOnHover", "\u53EF\u9009\uFF0C\u9ED8\u8BA4 `false`\u3002\u9F20\u6807\u60AC\u505C\u5728\u6574\u4E2A\u7269\u6599\u4E0A\u65F6\u6682\u505C\uFF0C\u79FB\u5F00\u540E\u91CD\u65B0\u8BA1\u65F6"]
881
+ ];
865
882
  var sections4 = [
866
883
  {
867
884
  id: "data",
@@ -931,9 +948,42 @@ var sections4 = [
931
948
  {
932
949
  t: "table",
933
950
  head: ["\u5B57\u6BB5", "\u8BF4\u660E"],
934
- rows: [
935
- ["list", "`data` \u91CC\u7684\u6570\u7EC4\u5B57\u6BB5\u540D"],
936
- ["indexBy", "`state` \u91CC\u7684\u4E0B\u6807\u5B57\u6BB5\u540D"]
951
+ rows: derivedRows.concat(
952
+ reconcileRows({ rows: derivedRows, actual: DERIVED_FIELDS, label: "\u65B0\u589E\u5B57\u6BB5" })
953
+ )
954
+ },
955
+ { t: "h3", text: "derived.autoplay\uFF1A\u81EA\u52A8\u8F6E\u64AD" },
956
+ {
957
+ t: "p",
958
+ text: "\u516C\u544A\u6761\u8F6E\u64AD\u6587\u6848\u3001\u8F6E\u64AD\u56FE\u8FD9\u7C7B\u300C\u9694\u51E0\u79D2\u81EA\u52A8\u6362\u4E0B\u4E00\u6761\u300D\u7684\u9700\u6C42\uFF0C\u5728 derived \u4E0A\u52A0 `autoplay` \u5373\u53EF\u3002\u914D\u4E86\u5C31\u5F00\u542F\u3001\u59CB\u7EC8\u5FAA\u73AF\uFF1A\u6BCF\u9694 `interval` \u6BEB\u79D2\u628A `state[indexBy]` \u52A0 1\uFF0C\u5230\u672B\u5C3E\u56DE\u5230 0\u3002\u5B83\u6539\u72B6\u6001\u8D70\u7684\u5C31\u662F\u70B9 tab \u90A3\u4E00\u6761\u8DEF\uFF0C\u6240\u4EE5\u6587\u6848\u3001\u56FE\u7247\u3001tabs \u5706\u70B9\u6307\u793A\u5668\u3001\u8DF3\u8F6C\u94FE\u63A5\u4F1A\u4E00\u8D77\u5207\uFF0C\u6548\u679C\u548C\u7528\u6237\u624B\u52A8\u70B9\u5B8C\u5168\u4E00\u6837\u2014\u2014\u4E0D\u4F1A\u51ED\u7A7A\u591A\u51FA\u5207\u6362\u52A8\u753B\uFF0C\u60F3\u8981\u8FC7\u6E21\u6548\u679C\u81EA\u5DF1\u5728 `style` \u91CC\u914D `transition`\u3002"
959
+ },
960
+ {
961
+ t: "code",
962
+ code: `"data": { "slides": [ { "text": "\u2026", "url": "\u2026" }, { \u2026 }, { \u2026 } ] },
963
+ "state": { "slide": 0 },
964
+ "derived": { "cur": { "list": "slides", "indexBy": "slide",
965
+ "autoplay": { "interval": 4000, "pauseOnHover": true } } }
966
+
967
+ // \u6587\u6848 {{ cur.text }}\u3001\u94FE\u63A5 {{ cur.url }} \u81EA\u52A8\u8DDF\u7740\u5F53\u524D\u9879\u8D70\uFF1B
968
+ // \u5706\u70B9\u6307\u793A\u5668\u5C31\u662F\u4E00\u4E2A tabs\uFF1A{ "type": "tabs", "bind": "slides", "stateKey": "slide" }`
969
+ },
970
+ {
971
+ t: "table",
972
+ head: ["\u5B57\u6BB5", "\u8BF4\u660E"],
973
+ rows: autoplayRows.concat(
974
+ reconcileRows({
975
+ rows: autoplayRows,
976
+ actual: AUTOPLAY_FIELDS,
977
+ label: "\u65B0\u589E\u5B57\u6BB5"
978
+ })
979
+ )
980
+ },
981
+ {
982
+ t: "list",
983
+ items: [
984
+ "\u7528\u6237\u70B9\u4E86 tab\uFF08\u6216\u4EFB\u4F55 `setState` \u6539\u4E86\u540C\u4E00\u4E2A key\uFF09\u4F1A**\u91CD\u65B0\u8BA1\u65F6**\uFF0C\u4E0D\u4F1A\u521A\u70B9\u5B8C\u5C31\u88AB\u5207\u8D70",
985
+ "\u5217\u8868\u53EA\u6709 0~1 \u6761\u3001\u6216\u6570\u636E\u6E90\u8FD8\u6CA1\u56DE\u6765\u65F6\u4E0D\u64AD\uFF1B\u6570\u636E\u56DE\u6765\u540E\u81EA\u52A8\u5F00\u59CB",
986
+ "\u6CA1\u6709 `enabled` / `loop` \u5F00\u5173\uFF1A\u4E0D\u60F3\u8F6E\u64AD\u5C31\u522B\u5199 `autoplay`\uFF0C\u5199\u4E86\u5C31\u4E00\u76F4\u5FAA\u73AF"
937
987
  ]
938
988
  }
939
989
  ]
@@ -1861,6 +1911,15 @@ var sections_default = sections10;
1861
1911
 
1862
1912
  // src/docs/meta.ts
1863
1913
  var changelog = [
1914
+ {
1915
+ date: "2026-09-22",
1916
+ items: [
1917
+ '\u65B0\u589E derived.autoplay \u81EA\u52A8\u8F6E\u64AD\uFF08\u7B2C 8 \u8282\uFF09\uFF1A`{ "interval": 4000, "pauseOnHover": true }` \u6302\u5728 derived \u4E0A\u5373\u53EF\u8BA9\u516C\u544A\u6761\u6587\u6848 / \u8F6E\u64AD\u56FE\u6BCF\u9694\u51E0\u79D2\u81EA\u52A8\u6362\u4E0B\u4E00\u6761\u3001\u5230\u672B\u5C3E\u56DE\u5230\u7B2C\u4E00\u6761\u3002\u5207\u6362\u8D70\u7684\u662F\u548C\u70B9 tab \u540C\u4E00\u6761\u8DEF\uFF0C\u6587\u6848\u3001\u56FE\u7247\u3001\u5706\u70B9\u6307\u793A\u5668\u3001\u8DF3\u8F6C\u94FE\u63A5\u4E00\u8D77\u5207\uFF1B\u7528\u6237\u624B\u52A8\u70B9\u8FC7\u4F1A\u91CD\u65B0\u8BA1\u65F6\uFF0C\u5217\u8868\u4E0D\u8DB3 2 \u6761\u6216\u6570\u636E\u6CA1\u56DE\u6765\u65F6\u4E0D\u64AD',
1918
+ "autoplay \u4FDD\u5B58\u65F6\u4F1A\u6821\u9A8C\uFF1Ainterval \u5FC5\u987B\u662F\u4E0D\u5C0F\u4E8E 300 \u7684\u6BEB\u79D2\u6570\uFF08\u5199\u6210 4 \u8868\u793A 4 \u6BEB\u79D2\uFF0C\u4F1A\u88AB\u62E6\u4E0B\uFF09\uFF0CpauseOnHover \u5FC5\u987B\u662F\u5E03\u5C14\uFF1B\u6CA1\u6709 enabled / loop \u5B57\u6BB5\uFF0C\u5199\u4E86\u53EA\u544A\u8B66\u5E76\u88AB\u5FFD\u7565",
1919
+ "\u65B0\u589E\u793A\u4F8B \u246D \u516C\u544A\u6761\u8F6E\u64AD\u6218\u62A5\uFF1A3 \u6761\u6587\u6848 + tabs \u5706\u70B9\u6307\u793A\u5668 + \u8DF3\u8F6C\u94FE\u63A5\u8DDF\u968F\u5F53\u524D\u9879\uFF0C\u8981\u505A\u8F6E\u64AD\u6284\u8FD9\u4EFD",
1920
+ "\u6846\u67B6\u58F3\u65B0\u589E\u4E00\u6761\u4E49\u52A1\uFF1A\u6E32\u67D3\u6811\u5E26 onRootEnter / onRootLeave \u65F6\u5728\u6839\u5BB9\u5668\u4E0A\u7ED1 mouseenter / mouseleave\uFF08Vue 2 \u58F3\u5DF2\u8DDF\u4E0A\uFF09"
1921
+ ]
1922
+ },
1864
1923
  {
1865
1924
  date: "2026-09-01",
1866
1925
  items: [
@@ -5336,6 +5395,142 @@ var quotaLow = {
5336
5395
  };
5337
5396
  var quota_low_default = quotaLow;
5338
5397
 
5398
+ // src/docs/examples/notice-carousel.ts
5399
+ var slides = [
5400
+ {
5401
+ key: "move",
5402
+ text: "\u{1F389} \u4ECA\u65E5\u5DF2\u6709 12,806 \u5BB6\u5E97\u94FA\u7528\u9EA6\u7206\u4E86\u5B8C\u6210\u5546\u54C1\u642C\u5BB6",
5403
+ url: "https://maibaole.example.com/move",
5404
+ // tabs 的标签字段,圆点不显示文字,留空串
5405
+ dot: ""
5406
+ },
5407
+ {
5408
+ key: "ai",
5409
+ text: "\u{1F525} AI \u667A\u80FD\u4F18\u5316\u672C\u5468\u7D2F\u8BA1\u6539\u5199\u6807\u9898 38 \u4E07\u6761\uFF0C\u70B9\u51FB\u7387\u5E73\u5747\u63D0\u5347 17%",
5410
+ url: "https://maibaole.example.com/ai-optimize",
5411
+ dot: ""
5412
+ },
5413
+ {
5414
+ key: "renew",
5415
+ text: "\u{1F4B0} \u4E13\u4E1A\u7248\u9650\u65F6 8 \u6298\uFF0C\u7EED\u8D39\u7ACB\u7701 180 \u5143",
5416
+ url: "https://maibaole.example.com/renew",
5417
+ dot: ""
5418
+ }
5419
+ ];
5420
+ var noticeCarousel = {
5421
+ version: 1,
5422
+ type: "notice",
5423
+ meta: { name: "\u516C\u544A\u6761\u8F6E\u64AD\u6218\u62A5\uFF08\u81EA\u52A8\u8F6E\u64AD\uFF09" },
5424
+ data: { slides },
5425
+ state: { slide: 0 },
5426
+ derived: {
5427
+ cur: {
5428
+ list: "slides",
5429
+ indexBy: "slide",
5430
+ // 4 秒一条,悬停暂停;单位是毫秒
5431
+ autoplay: { interval: 4e3, pauseOnHover: true }
5432
+ }
5433
+ },
5434
+ stage: {
5435
+ width: "100%",
5436
+ height: "auto",
5437
+ layout: "flow",
5438
+ // 通栏类型不写 maxWidth,宽度交给宿主坑位
5439
+ style: { radius: 6, background: "#fff8e6" },
5440
+ onShow: { type: "track", event: "notice_show", params: { notice: "carousel" } },
5441
+ onClose: {
5442
+ type: "track",
5443
+ event: "notice_dismiss",
5444
+ params: { notice: "carousel", reason: "{{ closeReason }}" }
5445
+ }
5446
+ },
5447
+ nodes: [
5448
+ {
5449
+ type: "flex",
5450
+ style: { align: "center", gap: 10, padding: "10px 14px" },
5451
+ children: [
5452
+ {
5453
+ // 文案跟着当前项走;定高单行,换条时公告条高度不跳
5454
+ type: "text",
5455
+ content: "{{ cur.text }}",
5456
+ style: {
5457
+ flex: "1",
5458
+ fontSize: 14,
5459
+ color: "#8a6212",
5460
+ lineHeight: 22,
5461
+ height: 22,
5462
+ overflow: "hidden",
5463
+ whiteSpace: "nowrap"
5464
+ }
5465
+ },
5466
+ {
5467
+ // 圆点指示器:当前项拉长成胶囊,宽度变化用 transition 过渡
5468
+ type: "tabs",
5469
+ bind: "slides",
5470
+ stateKey: "slide",
5471
+ labelField: "dot",
5472
+ style: { direction: "row", align: "center", gap: 6 },
5473
+ itemStyle: {
5474
+ width: 6,
5475
+ height: 6,
5476
+ radius: 3,
5477
+ background: "rgba(176,138,62,0.35)",
5478
+ cursor: "pointer",
5479
+ transition: "width 0.3s ease"
5480
+ },
5481
+ activeItemStyle: { width: 16, background: "#f5a623" },
5482
+ action: {
5483
+ type: "track",
5484
+ event: "notice_dot_click",
5485
+ params: { notice: "carousel" }
5486
+ }
5487
+ },
5488
+ {
5489
+ // 链接跟着当前项走:看到哪条、点的就是哪条
5490
+ type: "button",
5491
+ text: "\u53BB\u770B\u770B",
5492
+ style: {
5493
+ width: 64,
5494
+ height: 26,
5495
+ radius: 13,
5496
+ background: "#f5a623",
5497
+ color: "#fff",
5498
+ fontSize: 12,
5499
+ cursor: "pointer"
5500
+ },
5501
+ action: {
5502
+ type: "sequence",
5503
+ actions: [
5504
+ {
5505
+ type: "track",
5506
+ event: "notice_click",
5507
+ params: { notice: "carousel", slide: "{{ cur.key }}" }
5508
+ },
5509
+ { type: "navigate", url: "{{ cur.url }}", target: "_blank" }
5510
+ ]
5511
+ }
5512
+ },
5513
+ {
5514
+ type: "text",
5515
+ content: "\u2715",
5516
+ style: {
5517
+ alignSelf: "center",
5518
+ width: 22,
5519
+ height: 22,
5520
+ textAlign: "center",
5521
+ lineHeight: 22,
5522
+ color: "#b08a3e",
5523
+ fontSize: 13,
5524
+ cursor: "pointer"
5525
+ },
5526
+ action: { type: "close", reason: "user-close" }
5527
+ }
5528
+ ]
5529
+ }
5530
+ ]
5531
+ };
5532
+ var notice_carousel_default = noticeCarousel;
5533
+
5339
5534
  // src/docs/examples/index.ts
5340
5535
  var HOTSPOT_BG = "rgba(255,68,51,0.16)";
5341
5536
  var imageHotspot = {
@@ -5601,6 +5796,11 @@ var examples = [
5601
5796
  label: "\u246C \u590D\u523B\u8FD0\u8425 demo \xB7 \u642C\u5BB6\u6B21\u6570\u5C06\u5C3D\uFF08\u989D\u5EA6\u8FDB\u5EA6\u6761\uFF09",
5602
5797
  value: "quotaLow",
5603
5798
  dsl: quota_low_default
5799
+ },
5800
+ {
5801
+ label: "\u246D \u516C\u544A\u6761\u8F6E\u64AD\u6218\u62A5\uFF08\u81EA\u52A8\u8F6E\u64AD + \u5706\u70B9\u6307\u793A\u5668\uFF09",
5802
+ value: "noticeCarousel",
5803
+ dsl: notice_carousel_default
5604
5804
  }
5605
5805
  ];
5606
5806
  var examples_default = examples;
@@ -5779,6 +5979,7 @@ function checklistSection() {
5779
5979
  - [ ] \u6240\u6709 \`style\` \u7684\u5C5E\u6027\u540D\u90FD\u5728\u767D\u540D\u5355\u91CC
5780
5980
  - [ ] \u6CA1\u6709\u4EFB\u4F55 JS \u4EE3\u7801\u3001\`javascript:\` \u94FE\u63A5\u3001\u51FD\u6570\u8C03\u7528
5781
5981
  - [ ] \`derived.list\` \u6307\u5411\u7684\u5B57\u6BB5\u5728 \`data\` \u91CC\u771F\u5B9E\u5B58\u5728\uFF0C\`derived.indexBy\` \u5728 \`state\` \u91CC\u5B58\u5728
5982
+ - [ ] \u7528\u4E86 \`derived.autoplay\` \u7684\u8BDD\uFF1A\`interval\` \u662F \u2265${AUTOPLAY_MIN_INTERVAL} \u7684\u6BEB\u79D2\u6570\u5B57\uFF08\u4E0D\u662F\u79D2\uFF09\uFF0C\u6CA1\u6709\u5199 \`enabled\` / \`loop\` \u8FD9\u7C7B\u4E0D\u5B58\u5728\u7684\u5B57\u6BB5
5782
5983
  - [ ] \u7528\u4E86 \`views\` \u7684\u8BDD\uFF1A\`entry\` \u5B58\u5728\u4E8E \`views\`\uFF0C\u9876\u5C42\u6CA1\u6709\u6B8B\u7559 \`stage\`/\`nodes\`\uFF0C\u6240\u6709 \`open\` \u7684 \`view\` \u90FD\u80FD\u5BF9\u4E0A
5783
5984
  - [ ] \`repeat.bind\` \u6307\u5411\u7684\u662F\u6570\u7EC4
5784
5985
  - [ ] \u6BCF\u4E2A\u53EF\u70B9\u51FB\u5143\u7D20\u90FD\u6709 \`action\`\uFF0C\u4E14\u5E26 \`track\` \u57CB\u70B9
@@ -6028,6 +6229,18 @@ function dataSection() {
6028
6229
  \u4E4B\u540E\u4EFB\u610F\u8282\u70B9\u91CC\u7528 \`{{ current.price }}\` \`{{ current.url }}\`\uFF0C
6029
6230
  \u5207\u6362\u65F6\u4EF7\u683C\u3001\u94FE\u63A5\u3001\u57CB\u70B9\u53C2\u6570\u5168\u90E8\u81EA\u52A8\u8054\u52A8\uFF0C\u7248\u5F0F\u53EA\u9700\u8981\u6392\u4E00\u904D\u3002
6030
6231
 
6232
+ **\u81EA\u52A8\u8F6E\u64AD**\uFF08\u516C\u544A\u6761\u8F6E\u64AD\u6587\u6848 / \u6218\u62A5\u6EDA\u52A8\u3001\u8F6E\u64AD\u56FE\uFF09\u7528 \`derived.xxx.autoplay\`\uFF0C\u4E0D\u8981\u81EA\u5DF1\u62FC\u5B9A\u65F6\u903B\u8F91\uFF1A
6233
+
6234
+ \`\`\`jsonc
6235
+ "state": { "slide": 0 },
6236
+ "derived": { "cur": { "list": "slides", "indexBy": "slide",
6237
+ "autoplay": { "interval": 4000, "pauseOnHover": true } } }
6238
+ \`\`\`
6239
+
6240
+ - \`interval\` \u5FC5\u586B\uFF0C**\u5355\u4F4D\u6BEB\u79D2**\uFF0C\u4E0D\u5C0F\u4E8E ${AUTOPLAY_MIN_INTERVAL}\uFF1B\`pauseOnHover\` \u53EF\u9009\u5E03\u5C14\uFF08\u60AC\u505C\u6682\u505C\uFF09
6241
+ - \u53EA\u6709\u8FD9\u4E24\u4E2A\u5B57\u6BB5\uFF0C**\u6CA1\u6709** \`enabled\` / \`loop\`\uFF1A\u914D\u4E86\u5C31\u5F00\u542F\u3001\u59CB\u7EC8\u5FAA\u73AF
6242
+ - \u5207\u6362\u6548\u679C\u4E0E\u70B9 tab \u5B8C\u5168\u4E00\u6837\uFF0C\u6587\u6848 / \u56FE\u7247 / \u94FE\u63A5\u90FD\u4ECE \`{{ cur.xxx }}\` \u53D6\uFF1B\u8981\u5706\u70B9\u6307\u793A\u5668\u5C31\u653E\u4E00\u4E2A \`tabs\`\uFF08\`bind\` \u540C\u4E00\u4E2A\u5217\u8868\u3001\`stateKey\` \u540C\u4E00\u4E2A state\uFF09
6243
+
6031
6244
  \u6570\u636E\u8981\u4ECE\u540E\u7AEF\u53D6\u65F6\u5199 \`"tiers": { "$source": "\u6570\u636E\u6E90\u540D", "params": {} }\`\uFF0C
6032
6245
  \u6570\u636E\u6E90\u540D\u5FC5\u987B\u662F\u540E\u7AEF\u5DF2\u6CE8\u518C\u7684\uFF0C**\u4E0D\u80FD\u5199\u63A5\u53E3\u5730\u5740**\u3002\u7528\u6237\u6CA1\u8BF4\u5C31\u7528\u5199\u6B7B\u7684\u6570\u636E\u3002
6033
6246
 
@@ -1,4 +1,4 @@
1
- import { D as Dsl } from '../types-DDOAHxQt.cjs';
1
+ import { D as Dsl } from '../types-BapBTTio.cjs';
2
2
 
3
3
  /**
4
4
  * 开发文档的数据结构。
@@ -97,7 +97,8 @@ declare const meta: DocMeta;
97
97
  * ①② 是最小骨架,③④ 是运营 demo 真实弹窗的完整复刻,⑤⑥ 是多视图,⑦ 是换肤换布局,
98
98
  * ⑧ 是通栏 banner(宽度由页面决定、只能声明比例),⑨⑩ 是商品编辑抽屉里两个**固定尺寸**坑位,
99
99
  * ⑪⑫ 是搬家成功页两个线上弹窗的一比一复刻(动画、hover、相对时长倒计时的完整用法),
100
- * ⑬ 是额度进度条(用表达式算填充宽度,要画任何比例条都抄这份)。
100
+ * ⑬ 是额度进度条(用表达式算填充宽度,要画任何比例条都抄这份),
101
+ * ⑭ 是公告条自动轮播(derived.autoplay + tabs 圆点指示器)。
101
102
  * 后端数据源($source)用法见 ③。
102
103
  */
103
104
 
@@ -1,4 +1,4 @@
1
- import { D as Dsl } from '../types-DDOAHxQt.js';
1
+ import { D as Dsl } from '../types-BapBTTio.js';
2
2
 
3
3
  /**
4
4
  * 开发文档的数据结构。
@@ -97,7 +97,8 @@ declare const meta: DocMeta;
97
97
  * ①② 是最小骨架,③④ 是运营 demo 真实弹窗的完整复刻,⑤⑥ 是多视图,⑦ 是换肤换布局,
98
98
  * ⑧ 是通栏 banner(宽度由页面决定、只能声明比例),⑨⑩ 是商品编辑抽屉里两个**固定尺寸**坑位,
99
99
  * ⑪⑫ 是搬家成功页两个线上弹窗的一比一复刻(动画、hover、相对时长倒计时的完整用法),
100
- * ⑬ 是额度进度条(用表达式算填充宽度,要画任何比例条都抄这份)。
100
+ * ⑬ 是额度进度条(用表达式算填充宽度,要画任何比例条都抄这份),
101
+ * ⑭ 是公告条自动轮播(derived.autoplay + tabs 圆点指示器)。
101
102
  * 后端数据源($source)用法见 ③。
102
103
  */
103
104
 
@@ -1,4 +1,4 @@
1
- import { TRACK_EVENT_PATTERN, ALLOWED_EASINGS, DEFAULT_USER_FIELDS, USER_FIELD_LABELS, DSL_VERSION, ACTION_TYPES, NODE_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS } from '../chunk-DT3ZINDI.js';
1
+ import { TRACK_EVENT_PATTERN, ALLOWED_EASINGS, DEFAULT_USER_FIELDS, USER_FIELD_LABELS, AUTOPLAY_MIN_INTERVAL, DSL_VERSION, ACTION_TYPES, DERIVED_FIELDS, AUTOPLAY_FIELDS, NODE_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS } from '../chunk-B6BKOMWL.js';
2
2
 
3
3
  // src/docs/reconcile.ts
4
4
  function pickTokens(rows, columnIndex) {
@@ -701,6 +701,18 @@ var userFieldRows = DEFAULT_USER_FIELDS.map((field) => [
701
701
  `\`user.${field}\``,
702
702
  USER_FIELD_LABELS[field] || ""
703
703
  ]);
704
+ var derivedRows = [
705
+ ["list", "`data` \u91CC\u7684\u6570\u7EC4\u5B57\u6BB5\u540D"],
706
+ ["indexBy", "`state` \u91CC\u7684\u4E0B\u6807\u5B57\u6BB5\u540D"],
707
+ ["autoplay", "\u53EF\u9009\u3002\u81EA\u52A8\u8F6E\u64AD\uFF0C\u89C1\u4E0B"]
708
+ ];
709
+ var autoplayRows = [
710
+ [
711
+ "interval",
712
+ `\u5FC5\u586B\u3002\u5207\u6362\u95F4\u9694\uFF0C**\u5355\u4F4D\u6BEB\u79D2**\uFF0C\u4E0D\u5C0F\u4E8E ${AUTOPLAY_MIN_INTERVAL}\uFF08\u5982 \`4000\` = 4 \u79D2\uFF09`
713
+ ],
714
+ ["pauseOnHover", "\u53EF\u9009\uFF0C\u9ED8\u8BA4 `false`\u3002\u9F20\u6807\u60AC\u505C\u5728\u6574\u4E2A\u7269\u6599\u4E0A\u65F6\u6682\u505C\uFF0C\u79FB\u5F00\u540E\u91CD\u65B0\u8BA1\u65F6"]
715
+ ];
704
716
  var sections4 = [
705
717
  {
706
718
  id: "data",
@@ -770,9 +782,42 @@ var sections4 = [
770
782
  {
771
783
  t: "table",
772
784
  head: ["\u5B57\u6BB5", "\u8BF4\u660E"],
773
- rows: [
774
- ["list", "`data` \u91CC\u7684\u6570\u7EC4\u5B57\u6BB5\u540D"],
775
- ["indexBy", "`state` \u91CC\u7684\u4E0B\u6807\u5B57\u6BB5\u540D"]
785
+ rows: derivedRows.concat(
786
+ reconcileRows({ rows: derivedRows, actual: DERIVED_FIELDS, label: "\u65B0\u589E\u5B57\u6BB5" })
787
+ )
788
+ },
789
+ { t: "h3", text: "derived.autoplay\uFF1A\u81EA\u52A8\u8F6E\u64AD" },
790
+ {
791
+ t: "p",
792
+ text: "\u516C\u544A\u6761\u8F6E\u64AD\u6587\u6848\u3001\u8F6E\u64AD\u56FE\u8FD9\u7C7B\u300C\u9694\u51E0\u79D2\u81EA\u52A8\u6362\u4E0B\u4E00\u6761\u300D\u7684\u9700\u6C42\uFF0C\u5728 derived \u4E0A\u52A0 `autoplay` \u5373\u53EF\u3002\u914D\u4E86\u5C31\u5F00\u542F\u3001\u59CB\u7EC8\u5FAA\u73AF\uFF1A\u6BCF\u9694 `interval` \u6BEB\u79D2\u628A `state[indexBy]` \u52A0 1\uFF0C\u5230\u672B\u5C3E\u56DE\u5230 0\u3002\u5B83\u6539\u72B6\u6001\u8D70\u7684\u5C31\u662F\u70B9 tab \u90A3\u4E00\u6761\u8DEF\uFF0C\u6240\u4EE5\u6587\u6848\u3001\u56FE\u7247\u3001tabs \u5706\u70B9\u6307\u793A\u5668\u3001\u8DF3\u8F6C\u94FE\u63A5\u4F1A\u4E00\u8D77\u5207\uFF0C\u6548\u679C\u548C\u7528\u6237\u624B\u52A8\u70B9\u5B8C\u5168\u4E00\u6837\u2014\u2014\u4E0D\u4F1A\u51ED\u7A7A\u591A\u51FA\u5207\u6362\u52A8\u753B\uFF0C\u60F3\u8981\u8FC7\u6E21\u6548\u679C\u81EA\u5DF1\u5728 `style` \u91CC\u914D `transition`\u3002"
793
+ },
794
+ {
795
+ t: "code",
796
+ code: `"data": { "slides": [ { "text": "\u2026", "url": "\u2026" }, { \u2026 }, { \u2026 } ] },
797
+ "state": { "slide": 0 },
798
+ "derived": { "cur": { "list": "slides", "indexBy": "slide",
799
+ "autoplay": { "interval": 4000, "pauseOnHover": true } } }
800
+
801
+ // \u6587\u6848 {{ cur.text }}\u3001\u94FE\u63A5 {{ cur.url }} \u81EA\u52A8\u8DDF\u7740\u5F53\u524D\u9879\u8D70\uFF1B
802
+ // \u5706\u70B9\u6307\u793A\u5668\u5C31\u662F\u4E00\u4E2A tabs\uFF1A{ "type": "tabs", "bind": "slides", "stateKey": "slide" }`
803
+ },
804
+ {
805
+ t: "table",
806
+ head: ["\u5B57\u6BB5", "\u8BF4\u660E"],
807
+ rows: autoplayRows.concat(
808
+ reconcileRows({
809
+ rows: autoplayRows,
810
+ actual: AUTOPLAY_FIELDS,
811
+ label: "\u65B0\u589E\u5B57\u6BB5"
812
+ })
813
+ )
814
+ },
815
+ {
816
+ t: "list",
817
+ items: [
818
+ "\u7528\u6237\u70B9\u4E86 tab\uFF08\u6216\u4EFB\u4F55 `setState` \u6539\u4E86\u540C\u4E00\u4E2A key\uFF09\u4F1A**\u91CD\u65B0\u8BA1\u65F6**\uFF0C\u4E0D\u4F1A\u521A\u70B9\u5B8C\u5C31\u88AB\u5207\u8D70",
819
+ "\u5217\u8868\u53EA\u6709 0~1 \u6761\u3001\u6216\u6570\u636E\u6E90\u8FD8\u6CA1\u56DE\u6765\u65F6\u4E0D\u64AD\uFF1B\u6570\u636E\u56DE\u6765\u540E\u81EA\u52A8\u5F00\u59CB",
820
+ "\u6CA1\u6709 `enabled` / `loop` \u5F00\u5173\uFF1A\u4E0D\u60F3\u8F6E\u64AD\u5C31\u522B\u5199 `autoplay`\uFF0C\u5199\u4E86\u5C31\u4E00\u76F4\u5FAA\u73AF"
776
821
  ]
777
822
  }
778
823
  ]
@@ -1700,6 +1745,15 @@ var sections_default = sections10;
1700
1745
 
1701
1746
  // src/docs/meta.ts
1702
1747
  var changelog = [
1748
+ {
1749
+ date: "2026-09-22",
1750
+ items: [
1751
+ '\u65B0\u589E derived.autoplay \u81EA\u52A8\u8F6E\u64AD\uFF08\u7B2C 8 \u8282\uFF09\uFF1A`{ "interval": 4000, "pauseOnHover": true }` \u6302\u5728 derived \u4E0A\u5373\u53EF\u8BA9\u516C\u544A\u6761\u6587\u6848 / \u8F6E\u64AD\u56FE\u6BCF\u9694\u51E0\u79D2\u81EA\u52A8\u6362\u4E0B\u4E00\u6761\u3001\u5230\u672B\u5C3E\u56DE\u5230\u7B2C\u4E00\u6761\u3002\u5207\u6362\u8D70\u7684\u662F\u548C\u70B9 tab \u540C\u4E00\u6761\u8DEF\uFF0C\u6587\u6848\u3001\u56FE\u7247\u3001\u5706\u70B9\u6307\u793A\u5668\u3001\u8DF3\u8F6C\u94FE\u63A5\u4E00\u8D77\u5207\uFF1B\u7528\u6237\u624B\u52A8\u70B9\u8FC7\u4F1A\u91CD\u65B0\u8BA1\u65F6\uFF0C\u5217\u8868\u4E0D\u8DB3 2 \u6761\u6216\u6570\u636E\u6CA1\u56DE\u6765\u65F6\u4E0D\u64AD',
1752
+ "autoplay \u4FDD\u5B58\u65F6\u4F1A\u6821\u9A8C\uFF1Ainterval \u5FC5\u987B\u662F\u4E0D\u5C0F\u4E8E 300 \u7684\u6BEB\u79D2\u6570\uFF08\u5199\u6210 4 \u8868\u793A 4 \u6BEB\u79D2\uFF0C\u4F1A\u88AB\u62E6\u4E0B\uFF09\uFF0CpauseOnHover \u5FC5\u987B\u662F\u5E03\u5C14\uFF1B\u6CA1\u6709 enabled / loop \u5B57\u6BB5\uFF0C\u5199\u4E86\u53EA\u544A\u8B66\u5E76\u88AB\u5FFD\u7565",
1753
+ "\u65B0\u589E\u793A\u4F8B \u246D \u516C\u544A\u6761\u8F6E\u64AD\u6218\u62A5\uFF1A3 \u6761\u6587\u6848 + tabs \u5706\u70B9\u6307\u793A\u5668 + \u8DF3\u8F6C\u94FE\u63A5\u8DDF\u968F\u5F53\u524D\u9879\uFF0C\u8981\u505A\u8F6E\u64AD\u6284\u8FD9\u4EFD",
1754
+ "\u6846\u67B6\u58F3\u65B0\u589E\u4E00\u6761\u4E49\u52A1\uFF1A\u6E32\u67D3\u6811\u5E26 onRootEnter / onRootLeave \u65F6\u5728\u6839\u5BB9\u5668\u4E0A\u7ED1 mouseenter / mouseleave\uFF08Vue 2 \u58F3\u5DF2\u8DDF\u4E0A\uFF09"
1755
+ ]
1756
+ },
1703
1757
  {
1704
1758
  date: "2026-09-01",
1705
1759
  items: [
@@ -5175,6 +5229,142 @@ var quotaLow = {
5175
5229
  };
5176
5230
  var quota_low_default = quotaLow;
5177
5231
 
5232
+ // src/docs/examples/notice-carousel.ts
5233
+ var slides = [
5234
+ {
5235
+ key: "move",
5236
+ text: "\u{1F389} \u4ECA\u65E5\u5DF2\u6709 12,806 \u5BB6\u5E97\u94FA\u7528\u9EA6\u7206\u4E86\u5B8C\u6210\u5546\u54C1\u642C\u5BB6",
5237
+ url: "https://maibaole.example.com/move",
5238
+ // tabs 的标签字段,圆点不显示文字,留空串
5239
+ dot: ""
5240
+ },
5241
+ {
5242
+ key: "ai",
5243
+ text: "\u{1F525} AI \u667A\u80FD\u4F18\u5316\u672C\u5468\u7D2F\u8BA1\u6539\u5199\u6807\u9898 38 \u4E07\u6761\uFF0C\u70B9\u51FB\u7387\u5E73\u5747\u63D0\u5347 17%",
5244
+ url: "https://maibaole.example.com/ai-optimize",
5245
+ dot: ""
5246
+ },
5247
+ {
5248
+ key: "renew",
5249
+ text: "\u{1F4B0} \u4E13\u4E1A\u7248\u9650\u65F6 8 \u6298\uFF0C\u7EED\u8D39\u7ACB\u7701 180 \u5143",
5250
+ url: "https://maibaole.example.com/renew",
5251
+ dot: ""
5252
+ }
5253
+ ];
5254
+ var noticeCarousel = {
5255
+ version: 1,
5256
+ type: "notice",
5257
+ meta: { name: "\u516C\u544A\u6761\u8F6E\u64AD\u6218\u62A5\uFF08\u81EA\u52A8\u8F6E\u64AD\uFF09" },
5258
+ data: { slides },
5259
+ state: { slide: 0 },
5260
+ derived: {
5261
+ cur: {
5262
+ list: "slides",
5263
+ indexBy: "slide",
5264
+ // 4 秒一条,悬停暂停;单位是毫秒
5265
+ autoplay: { interval: 4e3, pauseOnHover: true }
5266
+ }
5267
+ },
5268
+ stage: {
5269
+ width: "100%",
5270
+ height: "auto",
5271
+ layout: "flow",
5272
+ // 通栏类型不写 maxWidth,宽度交给宿主坑位
5273
+ style: { radius: 6, background: "#fff8e6" },
5274
+ onShow: { type: "track", event: "notice_show", params: { notice: "carousel" } },
5275
+ onClose: {
5276
+ type: "track",
5277
+ event: "notice_dismiss",
5278
+ params: { notice: "carousel", reason: "{{ closeReason }}" }
5279
+ }
5280
+ },
5281
+ nodes: [
5282
+ {
5283
+ type: "flex",
5284
+ style: { align: "center", gap: 10, padding: "10px 14px" },
5285
+ children: [
5286
+ {
5287
+ // 文案跟着当前项走;定高单行,换条时公告条高度不跳
5288
+ type: "text",
5289
+ content: "{{ cur.text }}",
5290
+ style: {
5291
+ flex: "1",
5292
+ fontSize: 14,
5293
+ color: "#8a6212",
5294
+ lineHeight: 22,
5295
+ height: 22,
5296
+ overflow: "hidden",
5297
+ whiteSpace: "nowrap"
5298
+ }
5299
+ },
5300
+ {
5301
+ // 圆点指示器:当前项拉长成胶囊,宽度变化用 transition 过渡
5302
+ type: "tabs",
5303
+ bind: "slides",
5304
+ stateKey: "slide",
5305
+ labelField: "dot",
5306
+ style: { direction: "row", align: "center", gap: 6 },
5307
+ itemStyle: {
5308
+ width: 6,
5309
+ height: 6,
5310
+ radius: 3,
5311
+ background: "rgba(176,138,62,0.35)",
5312
+ cursor: "pointer",
5313
+ transition: "width 0.3s ease"
5314
+ },
5315
+ activeItemStyle: { width: 16, background: "#f5a623" },
5316
+ action: {
5317
+ type: "track",
5318
+ event: "notice_dot_click",
5319
+ params: { notice: "carousel" }
5320
+ }
5321
+ },
5322
+ {
5323
+ // 链接跟着当前项走:看到哪条、点的就是哪条
5324
+ type: "button",
5325
+ text: "\u53BB\u770B\u770B",
5326
+ style: {
5327
+ width: 64,
5328
+ height: 26,
5329
+ radius: 13,
5330
+ background: "#f5a623",
5331
+ color: "#fff",
5332
+ fontSize: 12,
5333
+ cursor: "pointer"
5334
+ },
5335
+ action: {
5336
+ type: "sequence",
5337
+ actions: [
5338
+ {
5339
+ type: "track",
5340
+ event: "notice_click",
5341
+ params: { notice: "carousel", slide: "{{ cur.key }}" }
5342
+ },
5343
+ { type: "navigate", url: "{{ cur.url }}", target: "_blank" }
5344
+ ]
5345
+ }
5346
+ },
5347
+ {
5348
+ type: "text",
5349
+ content: "\u2715",
5350
+ style: {
5351
+ alignSelf: "center",
5352
+ width: 22,
5353
+ height: 22,
5354
+ textAlign: "center",
5355
+ lineHeight: 22,
5356
+ color: "#b08a3e",
5357
+ fontSize: 13,
5358
+ cursor: "pointer"
5359
+ },
5360
+ action: { type: "close", reason: "user-close" }
5361
+ }
5362
+ ]
5363
+ }
5364
+ ]
5365
+ };
5366
+ var notice_carousel_default = noticeCarousel;
5367
+
5178
5368
  // src/docs/examples/index.ts
5179
5369
  var HOTSPOT_BG = "rgba(255,68,51,0.16)";
5180
5370
  var imageHotspot = {
@@ -5440,6 +5630,11 @@ var examples = [
5440
5630
  label: "\u246C \u590D\u523B\u8FD0\u8425 demo \xB7 \u642C\u5BB6\u6B21\u6570\u5C06\u5C3D\uFF08\u989D\u5EA6\u8FDB\u5EA6\u6761\uFF09",
5441
5631
  value: "quotaLow",
5442
5632
  dsl: quota_low_default
5633
+ },
5634
+ {
5635
+ label: "\u246D \u516C\u544A\u6761\u8F6E\u64AD\u6218\u62A5\uFF08\u81EA\u52A8\u8F6E\u64AD + \u5706\u70B9\u6307\u793A\u5668\uFF09",
5636
+ value: "noticeCarousel",
5637
+ dsl: notice_carousel_default
5443
5638
  }
5444
5639
  ];
5445
5640
  var examples_default = examples;
@@ -5618,6 +5813,7 @@ function checklistSection() {
5618
5813
  - [ ] \u6240\u6709 \`style\` \u7684\u5C5E\u6027\u540D\u90FD\u5728\u767D\u540D\u5355\u91CC
5619
5814
  - [ ] \u6CA1\u6709\u4EFB\u4F55 JS \u4EE3\u7801\u3001\`javascript:\` \u94FE\u63A5\u3001\u51FD\u6570\u8C03\u7528
5620
5815
  - [ ] \`derived.list\` \u6307\u5411\u7684\u5B57\u6BB5\u5728 \`data\` \u91CC\u771F\u5B9E\u5B58\u5728\uFF0C\`derived.indexBy\` \u5728 \`state\` \u91CC\u5B58\u5728
5816
+ - [ ] \u7528\u4E86 \`derived.autoplay\` \u7684\u8BDD\uFF1A\`interval\` \u662F \u2265${AUTOPLAY_MIN_INTERVAL} \u7684\u6BEB\u79D2\u6570\u5B57\uFF08\u4E0D\u662F\u79D2\uFF09\uFF0C\u6CA1\u6709\u5199 \`enabled\` / \`loop\` \u8FD9\u7C7B\u4E0D\u5B58\u5728\u7684\u5B57\u6BB5
5621
5817
  - [ ] \u7528\u4E86 \`views\` \u7684\u8BDD\uFF1A\`entry\` \u5B58\u5728\u4E8E \`views\`\uFF0C\u9876\u5C42\u6CA1\u6709\u6B8B\u7559 \`stage\`/\`nodes\`\uFF0C\u6240\u6709 \`open\` \u7684 \`view\` \u90FD\u80FD\u5BF9\u4E0A
5622
5818
  - [ ] \`repeat.bind\` \u6307\u5411\u7684\u662F\u6570\u7EC4
5623
5819
  - [ ] \u6BCF\u4E2A\u53EF\u70B9\u51FB\u5143\u7D20\u90FD\u6709 \`action\`\uFF0C\u4E14\u5E26 \`track\` \u57CB\u70B9
@@ -5867,6 +6063,18 @@ function dataSection() {
5867
6063
  \u4E4B\u540E\u4EFB\u610F\u8282\u70B9\u91CC\u7528 \`{{ current.price }}\` \`{{ current.url }}\`\uFF0C
5868
6064
  \u5207\u6362\u65F6\u4EF7\u683C\u3001\u94FE\u63A5\u3001\u57CB\u70B9\u53C2\u6570\u5168\u90E8\u81EA\u52A8\u8054\u52A8\uFF0C\u7248\u5F0F\u53EA\u9700\u8981\u6392\u4E00\u904D\u3002
5869
6065
 
6066
+ **\u81EA\u52A8\u8F6E\u64AD**\uFF08\u516C\u544A\u6761\u8F6E\u64AD\u6587\u6848 / \u6218\u62A5\u6EDA\u52A8\u3001\u8F6E\u64AD\u56FE\uFF09\u7528 \`derived.xxx.autoplay\`\uFF0C\u4E0D\u8981\u81EA\u5DF1\u62FC\u5B9A\u65F6\u903B\u8F91\uFF1A
6067
+
6068
+ \`\`\`jsonc
6069
+ "state": { "slide": 0 },
6070
+ "derived": { "cur": { "list": "slides", "indexBy": "slide",
6071
+ "autoplay": { "interval": 4000, "pauseOnHover": true } } }
6072
+ \`\`\`
6073
+
6074
+ - \`interval\` \u5FC5\u586B\uFF0C**\u5355\u4F4D\u6BEB\u79D2**\uFF0C\u4E0D\u5C0F\u4E8E ${AUTOPLAY_MIN_INTERVAL}\uFF1B\`pauseOnHover\` \u53EF\u9009\u5E03\u5C14\uFF08\u60AC\u505C\u6682\u505C\uFF09
6075
+ - \u53EA\u6709\u8FD9\u4E24\u4E2A\u5B57\u6BB5\uFF0C**\u6CA1\u6709** \`enabled\` / \`loop\`\uFF1A\u914D\u4E86\u5C31\u5F00\u542F\u3001\u59CB\u7EC8\u5FAA\u73AF
6076
+ - \u5207\u6362\u6548\u679C\u4E0E\u70B9 tab \u5B8C\u5168\u4E00\u6837\uFF0C\u6587\u6848 / \u56FE\u7247 / \u94FE\u63A5\u90FD\u4ECE \`{{ cur.xxx }}\` \u53D6\uFF1B\u8981\u5706\u70B9\u6307\u793A\u5668\u5C31\u653E\u4E00\u4E2A \`tabs\`\uFF08\`bind\` \u540C\u4E00\u4E2A\u5217\u8868\u3001\`stateKey\` \u540C\u4E00\u4E2A state\uFF09
6077
+
5870
6078
  \u6570\u636E\u8981\u4ECE\u540E\u7AEF\u53D6\u65F6\u5199 \`"tiers": { "$source": "\u6570\u636E\u6E90\u540D", "params": {} }\`\uFF0C
5871
6079
  \u6570\u636E\u6E90\u540D\u5FC5\u987B\u662F\u540E\u7AEF\u5DF2\u6CE8\u518C\u7684\uFF0C**\u4E0D\u80FD\u5199\u63A5\u53E3\u5730\u5740**\u3002\u7528\u6237\u6CA1\u8BF4\u5C31\u7528\u5199\u6B7B\u7684\u6570\u636E\u3002
5872
6080
 
package/dist/index.cjs CHANGED
@@ -500,6 +500,109 @@ function createTicker(options) {
500
500
  return { sync, stop };
501
501
  }
502
502
 
503
+ // src/autoplay.ts
504
+ var DERIVED_FIELDS = ["list", "indexBy", "autoplay"];
505
+ var AUTOPLAY_FIELDS = ["interval", "pauseOnHover"];
506
+ var AUTOPLAY_MIN_INTERVAL = 300;
507
+ function collectEntries(derived) {
508
+ const entries = [];
509
+ Object.keys(derived || {}).forEach((name) => {
510
+ const config = (derived || {})[name];
511
+ const autoplay = config && config.autoplay;
512
+ if (!autoplay || typeof autoplay !== "object") return;
513
+ const interval = Number(autoplay.interval);
514
+ if (!isFinite(interval) || interval < AUTOPLAY_MIN_INTERVAL) return;
515
+ entries.push({
516
+ list: config.list,
517
+ indexBy: config.indexBy,
518
+ interval,
519
+ pauseOnHover: autoplay.pauseOnHover === true,
520
+ timer: null
521
+ });
522
+ });
523
+ return entries;
524
+ }
525
+ function createAutoplay(options) {
526
+ const entries = collectEntries(options.derived);
527
+ let started = false;
528
+ let hovering = false;
529
+ let stopped = false;
530
+ function clear(entry) {
531
+ if (entry.timer) {
532
+ clearTimeout(entry.timer);
533
+ entry.timer = null;
534
+ }
535
+ }
536
+ function advance(entry) {
537
+ entry.timer = null;
538
+ const length = options.getLength(entry.list);
539
+ if (length <= 1) return;
540
+ const index = options.getIndex(entry.indexBy);
541
+ options.setState(entry.indexBy, (index + 1) % length);
542
+ }
543
+ function schedule(entry) {
544
+ clear(entry);
545
+ if (stopped || !started) return;
546
+ if (hovering && entry.pauseOnHover) return;
547
+ if (options.getLength(entry.list) <= 1) return;
548
+ entry.timer = setTimeout(() => advance(entry), entry.interval);
549
+ }
550
+ return {
551
+ pausable: entries.some((entry) => entry.pauseOnHover),
552
+ start() {
553
+ started = true;
554
+ entries.forEach(schedule);
555
+ },
556
+ reset(key) {
557
+ entries.forEach((entry) => {
558
+ if (entry.indexBy === key) schedule(entry);
559
+ });
560
+ },
561
+ pause() {
562
+ hovering = true;
563
+ entries.forEach((entry) => {
564
+ if (entry.pauseOnHover) clear(entry);
565
+ });
566
+ },
567
+ resume() {
568
+ hovering = false;
569
+ entries.forEach((entry) => {
570
+ if (entry.pauseOnHover) schedule(entry);
571
+ });
572
+ },
573
+ stop() {
574
+ stopped = true;
575
+ entries.forEach(clear);
576
+ }
577
+ };
578
+ }
579
+ function validateAutoplay(autoplay, path, add, warn) {
580
+ if (autoplay === void 0) return;
581
+ if (!autoplay || typeof autoplay !== "object" || Array.isArray(autoplay)) {
582
+ add(path, 'autoplay \u5FC5\u987B\u662F\u5BF9\u8C61\uFF0C\u5F62\u5982 { "interval": 4000, "pauseOnHover": true }');
583
+ return;
584
+ }
585
+ const config = autoplay;
586
+ const interval = config.interval;
587
+ if (typeof interval !== "number" || !isFinite(interval) || interval < AUTOPLAY_MIN_INTERVAL) {
588
+ add(
589
+ `${path}.interval`,
590
+ `interval \u5FC5\u987B\u662F\u4E0D\u5C0F\u4E8E ${AUTOPLAY_MIN_INTERVAL} \u7684\u6570\u5B57\uFF0C\u5355\u4F4D\u6BEB\u79D2\uFF08\u5982 4000 \u8868\u793A 4 \u79D2\uFF09\uFF0C\u5F53\u524D\u4E3A ${JSON.stringify(interval)}`
591
+ );
592
+ }
593
+ if (config.pauseOnHover !== void 0 && typeof config.pauseOnHover !== "boolean") {
594
+ add(`${path}.pauseOnHover`, "pauseOnHover \u5FC5\u987B\u662F true \u6216 false");
595
+ }
596
+ Object.keys(config).forEach((key) => {
597
+ if (AUTOPLAY_FIELDS.indexOf(key) === -1) {
598
+ warn(
599
+ `${path}.${key}`,
600
+ `autoplay \u4E0D\u8BA4\u8BC6 "${key}"\uFF0C\u4F1A\u88AB\u5FFD\u7565\u3002\u53EF\u7528\uFF1A${AUTOPLAY_FIELDS.join(" / ")}\uFF08\u914D\u4E86 autoplay \u5C31\u5F00\u542F\u4E14\u59CB\u7EC8\u5FAA\u73AF\uFF0C\u4E0D\u9700\u8981 enabled / loop\uFF09`
601
+ );
602
+ }
603
+ });
604
+ }
605
+
503
606
  // src/views.ts
504
607
  var SINGLE_VIEW_NAME = "main";
505
608
  function normalizeViews(dsl) {
@@ -1381,7 +1484,19 @@ function createRuntime(dsl, options = {}) {
1381
1484
  state = Object.assign({}, state, { [key]: value });
1382
1485
  emit("state-change", Object.assign({}, state));
1383
1486
  notify();
1487
+ autoplay.reset(key);
1384
1488
  }
1489
+ const autoplay = createAutoplay({
1490
+ derived: dsl.derived,
1491
+ getLength: (name) => {
1492
+ const list = resolvedData[name];
1493
+ return Array.isArray(list) ? list.length : 0;
1494
+ },
1495
+ getIndex: (key) => Number(state[key]) || 0,
1496
+ setState: (key, value) => {
1497
+ if (!destroyed) setState(key, value);
1498
+ }
1499
+ });
1385
1500
  function openView(name, mode) {
1386
1501
  if (!normalized.views[name]) {
1387
1502
  emit("error", { type: "unknown-view", name });
@@ -1482,6 +1597,7 @@ function createRuntime(dsl, options = {}) {
1482
1597
  notify();
1483
1598
  fireLifecycle(normalized.entry, "onShow");
1484
1599
  emit("ready", { keys: Object.keys(resolved) });
1600
+ autoplay.start();
1485
1601
  }).catch((error) => {
1486
1602
  if (destroyed) return;
1487
1603
  loading = false;
@@ -1510,6 +1626,10 @@ function createRuntime(dsl, options = {}) {
1510
1626
  hover
1511
1627
  });
1512
1628
  ticker.sync(tree.countdownEndTimes, tree.countdownPrecision);
1629
+ if (autoplay.pausable) {
1630
+ tree.onRootEnter = autoplay.pause;
1631
+ tree.onRootLeave = autoplay.resume;
1632
+ }
1513
1633
  return tree;
1514
1634
  }
1515
1635
  return {
@@ -1529,6 +1649,7 @@ function createRuntime(dsl, options = {}) {
1529
1649
  destroy() {
1530
1650
  destroyed = true;
1531
1651
  ticker.stop();
1652
+ autoplay.stop();
1532
1653
  listeners.clear();
1533
1654
  }
1534
1655
  };
@@ -1832,7 +1953,7 @@ function validate(dsl, options) {
1832
1953
  }
1833
1954
  const dataKeys = validateData(doc.data, add);
1834
1955
  const stateKeys = validateState(doc.state, add);
1835
- validateDerived(doc.derived, dataKeys, stateKeys, add);
1956
+ validateDerived(doc.derived, dataKeys, stateKeys, add, warn);
1836
1957
  const keyframeNames = validateKeyframes(doc.keyframes, add, warn);
1837
1958
  const isMulti = doc.views !== void 0;
1838
1959
  if (isMulti) validateMultiView(doc, add);
@@ -2081,7 +2202,7 @@ function validateState(state, add) {
2081
2202
  }
2082
2203
  return Object.keys(state);
2083
2204
  }
2084
- function validateDerived(derived, dataKeys, stateKeys, add) {
2205
+ function validateDerived(derived, dataKeys, stateKeys, add, warn) {
2085
2206
  if (derived === void 0) return;
2086
2207
  if (typeof derived !== "object" || derived === null || Array.isArray(derived)) {
2087
2208
  add("derived", "derived \u5FC5\u987B\u662F\u5BF9\u8C61");
@@ -2099,6 +2220,7 @@ function validateDerived(derived, dataKeys, stateKeys, add) {
2099
2220
  } else if (stateKeys.indexOf(item.indexBy) === -1) {
2100
2221
  add(`derived.${key}.indexBy`, `state \u4E2D\u4E0D\u5B58\u5728 "${item.indexBy}"`);
2101
2222
  }
2223
+ validateAutoplay(item.autoplay, `derived.${key}.autoplay`, add, warn);
2102
2224
  });
2103
2225
  }
2104
2226
  function validateNodeStyle(style, path, ctx) {
@@ -2334,10 +2456,13 @@ function isRegisteredHostHandler(name) {
2334
2456
  exports.ACTION_TYPES = ACTION_TYPES;
2335
2457
  exports.ALLOWED_EASINGS = ALLOWED_EASINGS;
2336
2458
  exports.ALLOWED_STYLE_KEYS = ALLOWED_STYLE_KEYS;
2459
+ exports.AUTOPLAY_FIELDS = AUTOPLAY_FIELDS;
2460
+ exports.AUTOPLAY_MIN_INTERVAL = AUTOPLAY_MIN_INTERVAL;
2337
2461
  exports.BANNER_MIN_ASPECT_RATIO = BANNER_MIN_ASPECT_RATIO;
2338
2462
  exports.CLOSE_POSITIONS = CLOSE_POSITIONS;
2339
2463
  exports.CSS_NAME_PATTERN = CSS_NAME_PATTERN;
2340
2464
  exports.DEFAULT_USER_FIELDS = DEFAULT_USER_FIELDS;
2465
+ exports.DERIVED_FIELDS = DERIVED_FIELDS;
2341
2466
  exports.DSL_VERSION = DSL_VERSION;
2342
2467
  exports.HOST_HANDLER_LABELS = HOST_HANDLER_LABELS;
2343
2468
  exports.HOST_HANDLER_NAMES = HOST_HANDLER_NAMES;
@@ -2369,4 +2494,5 @@ exports.sanitizeCssValue = sanitizeCssValue;
2369
2494
  exports.toCssStyle = toCssStyle;
2370
2495
  exports.toLength = toLength;
2371
2496
  exports.validate = validate;
2497
+ exports.validateAutoplay = validateAutoplay;
2372
2498
  exports.validateUserFields = validateUserFields;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as Dsl, R as RuntimeOptions, a as RenderTree, b as DslView, c as DslStyle, C as CssStyle } from './types-DDOAHxQt.cjs';
2
- export { s as DslAction, j as DslActionBase, h as DslAnimate, q as DslCallAction, l as DslCloseAction, m as DslCloseAllAction, v as DslCloseButton, u as DslClosePosition, y as DslDerived, E as DslHandler, G as DslInteractionEvent, F as DslInteractionTrigger, i as DslKeyframe, f as DslLength, k as DslNavigateAction, t as DslNode, e as DslNodeType, n as DslOpenAction, g as DslRect, r as DslSequenceAction, o as DslSetStateAction, B as DslSource, x as DslSourceRef, w as DslStage, p as DslTrackAction, d as DslViewType, z as RenderElement, A as RenderLayer, J as RuntimeEmit, I as RuntimeEventName, H as RuntimeEvents } from './types-DDOAHxQt.cjs';
1
+ import { D as Dsl, R as RuntimeOptions, a as RenderTree, b as DslView, c as DslStyle, C as CssStyle } from './types-BapBTTio.cjs';
2
+ export { s as DslAction, j as DslActionBase, h as DslAnimate, y as DslAutoplay, q as DslCallAction, l as DslCloseAction, m as DslCloseAllAction, v as DslCloseButton, u as DslClosePosition, z as DslDerived, F as DslHandler, H as DslInteractionEvent, G as DslInteractionTrigger, i as DslKeyframe, f as DslLength, k as DslNavigateAction, t as DslNode, e as DslNodeType, n as DslOpenAction, g as DslRect, r as DslSequenceAction, o as DslSetStateAction, E as DslSource, x as DslSourceRef, w as DslStage, p as DslTrackAction, d as DslViewType, A as RenderElement, B as RenderLayer, K as RuntimeEmit, J as RuntimeEventName, I as RuntimeEvents } from './types-BapBTTio.cjs';
3
3
 
4
4
  /**
5
5
  * 运行时。
@@ -28,7 +28,7 @@ interface DslRuntime {
28
28
  /** 供调试台等外部读取,正常渲染不需要 */
29
29
  getState: () => Record<string, unknown>;
30
30
  getViewStack: () => string[];
31
- /** 卸载时必须调用,否则倒计时定时器不会停 */
31
+ /** 卸载时必须调用,否则倒计时与自动轮播的定时器不会停 */
32
32
  destroy: () => void;
33
33
  }
34
34
  declare function createRuntime(dsl: Dsl, options?: RuntimeOptions): DslRuntime;
@@ -372,4 +372,41 @@ declare function computeParts(endTime: number, now?: number): CountdownParts;
372
372
  /** 不给 children 时退化成一行文本,占位符 {d} {h} {hAll} {m} {s} {cs} */
373
373
  declare function formatParts(parts: CountdownParts, format?: string): string;
374
374
 
375
- export { ACTION_TYPES, ALLOWED_EASINGS, ALLOWED_STYLE_KEYS, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, CSS_NAME_PATTERN, type CountdownParts, type CountdownPrecision, CssStyle, DEFAULT_USER_FIELDS, DSL_VERSION, Dsl, type DslRuntime, DslStyle, DslView, HOST_HANDLER_LABELS, HOST_HANDLER_NAMES, HOST_SOURCE_LABELS, HOST_SOURCE_NAMES, type HostHandlerName, type HostSourceName, type Issue, NODE_TYPES, type NormalizedViews, RenderTree, RuntimeOptions, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, type ValidateOptions, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isKeyframeOffset, isLength, isRegisteredHostHandler, isRegisteredHostSource, isValidEasing, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, sanitizeCssValue, toCssStyle, toLength, validate, validateUserFields };
375
+ /**
376
+ * derived 自动轮播(`derived.xxx.autoplay`)。
377
+ *
378
+ * 只管「什么时候该往下切一格」,**不管切了之后长什么样**:
379
+ * 到点后调的是运行时的同一个 `setState`,和用户点 tab 完全同一条路径——
380
+ * 文案 / 图片 / tabs 指示器 / 跳转链接 / 运营自己配的 transition 全部自然联动,
381
+ * 这里不引入任何额外的切换动画,也不另写一套状态改动。
382
+ *
383
+ * 时机规则:
384
+ * - 数据就绪(`start`)之后才开始计时;列表长度 ≤1 不播
385
+ * - 用户手动改了同一个 state key(`reset`)→ 重新计时,避免刚点完就被切走
386
+ * - 悬停(`pause`)清掉定时器,离开(`resume`)重新计一整个 interval
387
+ * - `stop` 之后永久失效,任何入口都不会再起定时器
388
+ *
389
+ * 不依赖 DOM,定时器用 setTimeout,Node 里也能跑。
390
+ */
391
+
392
+ /** `derived.xxx` 允许出现的字段,校验与文档对账共用这一份 */
393
+ declare const DERIVED_FIELDS: string[];
394
+ /** `derived.xxx.autoplay` 允许出现的字段,校验与文档对账共用这一份 */
395
+ declare const AUTOPLAY_FIELDS: string[];
396
+ /** 轮播间隔下限(毫秒)。主要防单位写错(把 4 秒写成 4),300 仍允许快速闪动类效果 */
397
+ declare const AUTOPLAY_MIN_INTERVAL = 300;
398
+ type Add = (path: string, message: string) => void;
399
+ /**
400
+ * 校验一处 `derived.xxx.autoplay`。
401
+ *
402
+ * 和运行时共用同一套常量(字段表、间隔下限),规则不会两边漂移。
403
+ * interval 写错的表现是「不轮播」,运营只会以为没生效、反复重配,所以要在保存时拦下。
404
+ *
405
+ * @param autoplay 待校验的值
406
+ * @param path 报错路径,如 `derived.cur.autoplay`
407
+ * @param add 报 error
408
+ * @param warn 报 warning(未知字段只告警:运行时会忽略,但多半是写错了,如 loop / enabled)
409
+ */
410
+ declare function validateAutoplay(autoplay: unknown, path: string, add: Add, warn: Add): void;
411
+
412
+ export { ACTION_TYPES, ALLOWED_EASINGS, ALLOWED_STYLE_KEYS, AUTOPLAY_FIELDS, AUTOPLAY_MIN_INTERVAL, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, CSS_NAME_PATTERN, type CountdownParts, type CountdownPrecision, CssStyle, DEFAULT_USER_FIELDS, DERIVED_FIELDS, DSL_VERSION, Dsl, type DslRuntime, DslStyle, DslView, HOST_HANDLER_LABELS, HOST_HANDLER_NAMES, HOST_SOURCE_LABELS, HOST_SOURCE_NAMES, type HostHandlerName, type HostSourceName, type Issue, NODE_TYPES, type NormalizedViews, RenderTree, RuntimeOptions, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, type ValidateOptions, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isKeyframeOffset, isLength, isRegisteredHostHandler, isRegisteredHostSource, isValidEasing, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, sanitizeCssValue, toCssStyle, toLength, validate, validateAutoplay, validateUserFields };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as Dsl, R as RuntimeOptions, a as RenderTree, b as DslView, c as DslStyle, C as CssStyle } from './types-DDOAHxQt.js';
2
- export { s as DslAction, j as DslActionBase, h as DslAnimate, q as DslCallAction, l as DslCloseAction, m as DslCloseAllAction, v as DslCloseButton, u as DslClosePosition, y as DslDerived, E as DslHandler, G as DslInteractionEvent, F as DslInteractionTrigger, i as DslKeyframe, f as DslLength, k as DslNavigateAction, t as DslNode, e as DslNodeType, n as DslOpenAction, g as DslRect, r as DslSequenceAction, o as DslSetStateAction, B as DslSource, x as DslSourceRef, w as DslStage, p as DslTrackAction, d as DslViewType, z as RenderElement, A as RenderLayer, J as RuntimeEmit, I as RuntimeEventName, H as RuntimeEvents } from './types-DDOAHxQt.js';
1
+ import { D as Dsl, R as RuntimeOptions, a as RenderTree, b as DslView, c as DslStyle, C as CssStyle } from './types-BapBTTio.js';
2
+ export { s as DslAction, j as DslActionBase, h as DslAnimate, y as DslAutoplay, q as DslCallAction, l as DslCloseAction, m as DslCloseAllAction, v as DslCloseButton, u as DslClosePosition, z as DslDerived, F as DslHandler, H as DslInteractionEvent, G as DslInteractionTrigger, i as DslKeyframe, f as DslLength, k as DslNavigateAction, t as DslNode, e as DslNodeType, n as DslOpenAction, g as DslRect, r as DslSequenceAction, o as DslSetStateAction, E as DslSource, x as DslSourceRef, w as DslStage, p as DslTrackAction, d as DslViewType, A as RenderElement, B as RenderLayer, K as RuntimeEmit, J as RuntimeEventName, I as RuntimeEvents } from './types-BapBTTio.js';
3
3
 
4
4
  /**
5
5
  * 运行时。
@@ -28,7 +28,7 @@ interface DslRuntime {
28
28
  /** 供调试台等外部读取,正常渲染不需要 */
29
29
  getState: () => Record<string, unknown>;
30
30
  getViewStack: () => string[];
31
- /** 卸载时必须调用,否则倒计时定时器不会停 */
31
+ /** 卸载时必须调用,否则倒计时与自动轮播的定时器不会停 */
32
32
  destroy: () => void;
33
33
  }
34
34
  declare function createRuntime(dsl: Dsl, options?: RuntimeOptions): DslRuntime;
@@ -372,4 +372,41 @@ declare function computeParts(endTime: number, now?: number): CountdownParts;
372
372
  /** 不给 children 时退化成一行文本,占位符 {d} {h} {hAll} {m} {s} {cs} */
373
373
  declare function formatParts(parts: CountdownParts, format?: string): string;
374
374
 
375
- export { ACTION_TYPES, ALLOWED_EASINGS, ALLOWED_STYLE_KEYS, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, CSS_NAME_PATTERN, type CountdownParts, type CountdownPrecision, CssStyle, DEFAULT_USER_FIELDS, DSL_VERSION, Dsl, type DslRuntime, DslStyle, DslView, HOST_HANDLER_LABELS, HOST_HANDLER_NAMES, HOST_SOURCE_LABELS, HOST_SOURCE_NAMES, type HostHandlerName, type HostSourceName, type Issue, NODE_TYPES, type NormalizedViews, RenderTree, RuntimeOptions, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, type ValidateOptions, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isKeyframeOffset, isLength, isRegisteredHostHandler, isRegisteredHostSource, isValidEasing, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, sanitizeCssValue, toCssStyle, toLength, validate, validateUserFields };
375
+ /**
376
+ * derived 自动轮播(`derived.xxx.autoplay`)。
377
+ *
378
+ * 只管「什么时候该往下切一格」,**不管切了之后长什么样**:
379
+ * 到点后调的是运行时的同一个 `setState`,和用户点 tab 完全同一条路径——
380
+ * 文案 / 图片 / tabs 指示器 / 跳转链接 / 运营自己配的 transition 全部自然联动,
381
+ * 这里不引入任何额外的切换动画,也不另写一套状态改动。
382
+ *
383
+ * 时机规则:
384
+ * - 数据就绪(`start`)之后才开始计时;列表长度 ≤1 不播
385
+ * - 用户手动改了同一个 state key(`reset`)→ 重新计时,避免刚点完就被切走
386
+ * - 悬停(`pause`)清掉定时器,离开(`resume`)重新计一整个 interval
387
+ * - `stop` 之后永久失效,任何入口都不会再起定时器
388
+ *
389
+ * 不依赖 DOM,定时器用 setTimeout,Node 里也能跑。
390
+ */
391
+
392
+ /** `derived.xxx` 允许出现的字段,校验与文档对账共用这一份 */
393
+ declare const DERIVED_FIELDS: string[];
394
+ /** `derived.xxx.autoplay` 允许出现的字段,校验与文档对账共用这一份 */
395
+ declare const AUTOPLAY_FIELDS: string[];
396
+ /** 轮播间隔下限(毫秒)。主要防单位写错(把 4 秒写成 4),300 仍允许快速闪动类效果 */
397
+ declare const AUTOPLAY_MIN_INTERVAL = 300;
398
+ type Add = (path: string, message: string) => void;
399
+ /**
400
+ * 校验一处 `derived.xxx.autoplay`。
401
+ *
402
+ * 和运行时共用同一套常量(字段表、间隔下限),规则不会两边漂移。
403
+ * interval 写错的表现是「不轮播」,运营只会以为没生效、反复重配,所以要在保存时拦下。
404
+ *
405
+ * @param autoplay 待校验的值
406
+ * @param path 报错路径,如 `derived.cur.autoplay`
407
+ * @param add 报 error
408
+ * @param warn 报 warning(未知字段只告警:运行时会忽略,但多半是写错了,如 loop / enabled)
409
+ */
410
+ declare function validateAutoplay(autoplay: unknown, path: string, add: Add, warn: Add): void;
411
+
412
+ export { ACTION_TYPES, ALLOWED_EASINGS, ALLOWED_STYLE_KEYS, AUTOPLAY_FIELDS, AUTOPLAY_MIN_INTERVAL, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, CSS_NAME_PATTERN, type CountdownParts, type CountdownPrecision, CssStyle, DEFAULT_USER_FIELDS, DERIVED_FIELDS, DSL_VERSION, Dsl, type DslRuntime, DslStyle, DslView, HOST_HANDLER_LABELS, HOST_HANDLER_NAMES, HOST_SOURCE_LABELS, HOST_SOURCE_NAMES, type HostHandlerName, type HostSourceName, type Issue, NODE_TYPES, type NormalizedViews, RenderTree, RuntimeOptions, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, type ValidateOptions, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isKeyframeOffset, isLength, isRegisteredHostHandler, isRegisteredHostSource, isValidEasing, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, sanitizeCssValue, toCssStyle, toLength, validate, validateAutoplay, validateUserFields };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { normalizeViews, nextScopeUid, createTicker, createCssBuilder, declarations, toCssStyle, interpolateDeep, evaluate, resolveNodeStyle, interpolate, toLength, computeParts, formatParts, parseEndTime } from './chunk-DT3ZINDI.js';
2
- export { ACTION_TYPES, ALLOWED_EASINGS, ALLOWED_STYLE_KEYS, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, CSS_NAME_PATTERN, DEFAULT_USER_FIELDS, DSL_VERSION, NODE_TYPES, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, check, computeParts, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isKeyframeOffset, isLength, isValidEasing, isValidEndTime, normalizeViews, parseEndTime, sanitizeCssValue, toCssStyle, toLength, validate, validateUserFields } from './chunk-DT3ZINDI.js';
1
+ import { normalizeViews, nextScopeUid, createTicker, createAutoplay, createCssBuilder, declarations, toCssStyle, interpolateDeep, evaluate, resolveNodeStyle, interpolate, toLength, computeParts, formatParts, parseEndTime } from './chunk-B6BKOMWL.js';
2
+ export { ACTION_TYPES, ALLOWED_EASINGS, ALLOWED_STYLE_KEYS, AUTOPLAY_FIELDS, AUTOPLAY_MIN_INTERVAL, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, CSS_NAME_PATTERN, DEFAULT_USER_FIELDS, DERIVED_FIELDS, DSL_VERSION, NODE_TYPES, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, check, computeParts, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isKeyframeOffset, isLength, isValidEasing, isValidEndTime, normalizeViews, parseEndTime, sanitizeCssValue, toCssStyle, toLength, validate, validateAutoplay, validateUserFields } from './chunk-B6BKOMWL.js';
3
3
 
4
4
  // src/url.ts
5
5
  var SAFE_PROTOCOLS = ["http:", "https:", "mailto:", "tel:"];
@@ -716,7 +716,19 @@ function createRuntime(dsl, options = {}) {
716
716
  state = Object.assign({}, state, { [key]: value });
717
717
  emit("state-change", Object.assign({}, state));
718
718
  notify();
719
+ autoplay.reset(key);
719
720
  }
721
+ const autoplay = createAutoplay({
722
+ derived: dsl.derived,
723
+ getLength: (name) => {
724
+ const list = resolvedData[name];
725
+ return Array.isArray(list) ? list.length : 0;
726
+ },
727
+ getIndex: (key) => Number(state[key]) || 0,
728
+ setState: (key, value) => {
729
+ if (!destroyed) setState(key, value);
730
+ }
731
+ });
720
732
  function openView(name, mode) {
721
733
  if (!normalized.views[name]) {
722
734
  emit("error", { type: "unknown-view", name });
@@ -817,6 +829,7 @@ function createRuntime(dsl, options = {}) {
817
829
  notify();
818
830
  fireLifecycle(normalized.entry, "onShow");
819
831
  emit("ready", { keys: Object.keys(resolved) });
832
+ autoplay.start();
820
833
  }).catch((error) => {
821
834
  if (destroyed) return;
822
835
  loading = false;
@@ -845,6 +858,10 @@ function createRuntime(dsl, options = {}) {
845
858
  hover
846
859
  });
847
860
  ticker.sync(tree.countdownEndTimes, tree.countdownPrecision);
861
+ if (autoplay.pausable) {
862
+ tree.onRootEnter = autoplay.pause;
863
+ tree.onRootLeave = autoplay.resume;
864
+ }
848
865
  return tree;
849
866
  }
850
867
  return {
@@ -864,6 +881,7 @@ function createRuntime(dsl, options = {}) {
864
881
  destroy() {
865
882
  destroyed = true;
866
883
  ticker.stop();
884
+ autoplay.stop();
867
885
  listeners.clear();
868
886
  }
869
887
  };
@@ -1,4 +1,4 @@
1
- import { I as RuntimeEventName, G as DslInteractionEvent } from '../types-DDOAHxQt.cjs';
1
+ import { J as RuntimeEventName, H as DslInteractionEvent } from '../types-BapBTTio.cjs';
2
2
 
3
3
  /**
4
4
  * 营销物料上报载荷类型(上报契约的唯一数据源)。
@@ -1,4 +1,4 @@
1
- import { I as RuntimeEventName, G as DslInteractionEvent } from '../types-DDOAHxQt.js';
1
+ import { J as RuntimeEventName, H as DslInteractionEvent } from '../types-BapBTTio.js';
2
2
 
3
3
  /**
4
4
  * 营销物料上报载荷类型(上报契约的唯一数据源)。
@@ -213,11 +213,25 @@ interface DslSourceRef {
213
213
  $source: string;
214
214
  params?: Record<string, unknown>;
215
215
  }
216
+ /**
217
+ * 自动轮播配置,挂在 `derived.xxx.autoplay` 上。
218
+ *
219
+ * 配了就开启、始终循环:每隔 `interval` 毫秒把 `state[indexBy]` 往下推一格
220
+ * (到末尾回到 0)。走的是和用户点 tab 同一个 setState,效果与手动切换完全一致。
221
+ */
222
+ interface DslAutoplay {
223
+ /** 切换间隔,毫秒,不小于 1000 */
224
+ interval: number;
225
+ /** 鼠标悬停在整个物料上时暂停,离开后重新计时。默认 false */
226
+ pauseOnHover?: boolean;
227
+ }
216
228
  interface DslDerived {
217
229
  /** data 里的数组字段名 */
218
230
  list: string;
219
231
  /** state 里的下标字段名 */
220
232
  indexBy: string;
233
+ /** 自动轮播;不写就不轮播 */
234
+ autoplay?: DslAutoplay;
221
235
  }
222
236
  /** 一份完整配置。单视图直接写 type/stage/nodes,多视图写 views + entry */
223
237
  interface Dsl extends Partial<DslView> {
@@ -341,6 +355,15 @@ interface RenderTree {
341
355
  * 同一个页面上出现多份物料时,各自的 hover 规则不会串台。
342
356
  */
343
357
  rootClassName: string;
358
+ /**
359
+ * 鼠标进入根容器时调用。只有配置里存在 `pauseOnHover: true` 的自动轮播时才有值。
360
+ *
361
+ * 框架壳的义务:有值时在根容器上绑 `mouseenter` 调它,**不阻止冒泡、不加任何判断**;
362
+ * 没值时什么都不绑。暂停哪些轮播、何时恢复,全由 core 决定。
363
+ */
364
+ onRootEnter?: () => void;
365
+ /** 鼠标离开根容器时调用,与 `onRootEnter` 成对出现,壳绑 `mouseleave` */
366
+ onRootLeave?: () => void;
344
367
  }
345
368
  type DslSource = (params: Record<string, unknown>, user: Record<string, unknown>) => unknown | Promise<unknown>;
346
369
  type DslHandler = (params: Record<string, unknown>) => void;
@@ -461,4 +484,4 @@ interface RuntimeOptions {
461
484
  emit?: RuntimeEmit;
462
485
  }
463
486
 
464
- export type { RenderLayer as A, DslSource as B, CssStyle as C, Dsl as D, DslHandler as E, DslInteractionTrigger as F, DslInteractionEvent as G, RuntimeEvents as H, RuntimeEventName as I, RuntimeEmit as J, RuntimeOptions as R, RenderTree as a, DslView as b, DslStyle as c, DslViewType as d, DslNodeType as e, DslLength as f, DslRect as g, DslAnimate as h, DslKeyframe as i, DslActionBase as j, DslNavigateAction as k, DslCloseAction as l, DslCloseAllAction as m, DslOpenAction as n, DslSetStateAction as o, DslTrackAction as p, DslCallAction as q, DslSequenceAction as r, DslAction as s, DslNode as t, DslClosePosition as u, DslCloseButton as v, DslStage as w, DslSourceRef as x, DslDerived as y, RenderElement as z };
487
+ export type { RenderElement as A, RenderLayer as B, CssStyle as C, Dsl as D, DslSource as E, DslHandler as F, DslInteractionTrigger as G, DslInteractionEvent as H, RuntimeEvents as I, RuntimeEventName as J, RuntimeEmit as K, RuntimeOptions as R, RenderTree as a, DslView as b, DslStyle as c, DslViewType as d, DslNodeType as e, DslLength as f, DslRect as g, DslAnimate as h, DslKeyframe as i, DslActionBase as j, DslNavigateAction as k, DslCloseAction as l, DslCloseAllAction as m, DslOpenAction as n, DslSetStateAction as o, DslTrackAction as p, DslCallAction as q, DslSequenceAction as r, DslAction as s, DslNode as t, DslClosePosition as u, DslCloseButton as v, DslStage as w, DslSourceRef as x, DslAutoplay as y, DslDerived as z };
@@ -213,11 +213,25 @@ interface DslSourceRef {
213
213
  $source: string;
214
214
  params?: Record<string, unknown>;
215
215
  }
216
+ /**
217
+ * 自动轮播配置,挂在 `derived.xxx.autoplay` 上。
218
+ *
219
+ * 配了就开启、始终循环:每隔 `interval` 毫秒把 `state[indexBy]` 往下推一格
220
+ * (到末尾回到 0)。走的是和用户点 tab 同一个 setState,效果与手动切换完全一致。
221
+ */
222
+ interface DslAutoplay {
223
+ /** 切换间隔,毫秒,不小于 1000 */
224
+ interval: number;
225
+ /** 鼠标悬停在整个物料上时暂停,离开后重新计时。默认 false */
226
+ pauseOnHover?: boolean;
227
+ }
216
228
  interface DslDerived {
217
229
  /** data 里的数组字段名 */
218
230
  list: string;
219
231
  /** state 里的下标字段名 */
220
232
  indexBy: string;
233
+ /** 自动轮播;不写就不轮播 */
234
+ autoplay?: DslAutoplay;
221
235
  }
222
236
  /** 一份完整配置。单视图直接写 type/stage/nodes,多视图写 views + entry */
223
237
  interface Dsl extends Partial<DslView> {
@@ -341,6 +355,15 @@ interface RenderTree {
341
355
  * 同一个页面上出现多份物料时,各自的 hover 规则不会串台。
342
356
  */
343
357
  rootClassName: string;
358
+ /**
359
+ * 鼠标进入根容器时调用。只有配置里存在 `pauseOnHover: true` 的自动轮播时才有值。
360
+ *
361
+ * 框架壳的义务:有值时在根容器上绑 `mouseenter` 调它,**不阻止冒泡、不加任何判断**;
362
+ * 没值时什么都不绑。暂停哪些轮播、何时恢复,全由 core 决定。
363
+ */
364
+ onRootEnter?: () => void;
365
+ /** 鼠标离开根容器时调用,与 `onRootEnter` 成对出现,壳绑 `mouseleave` */
366
+ onRootLeave?: () => void;
344
367
  }
345
368
  type DslSource = (params: Record<string, unknown>, user: Record<string, unknown>) => unknown | Promise<unknown>;
346
369
  type DslHandler = (params: Record<string, unknown>) => void;
@@ -461,4 +484,4 @@ interface RuntimeOptions {
461
484
  emit?: RuntimeEmit;
462
485
  }
463
486
 
464
- export type { RenderLayer as A, DslSource as B, CssStyle as C, Dsl as D, DslHandler as E, DslInteractionTrigger as F, DslInteractionEvent as G, RuntimeEvents as H, RuntimeEventName as I, RuntimeEmit as J, RuntimeOptions as R, RenderTree as a, DslView as b, DslStyle as c, DslViewType as d, DslNodeType as e, DslLength as f, DslRect as g, DslAnimate as h, DslKeyframe as i, DslActionBase as j, DslNavigateAction as k, DslCloseAction as l, DslCloseAllAction as m, DslOpenAction as n, DslSetStateAction as o, DslTrackAction as p, DslCallAction as q, DslSequenceAction as r, DslAction as s, DslNode as t, DslClosePosition as u, DslCloseButton as v, DslStage as w, DslSourceRef as x, DslDerived as y, RenderElement as z };
487
+ export type { RenderElement as A, RenderLayer as B, CssStyle as C, Dsl as D, DslSource as E, DslHandler as F, DslInteractionTrigger as G, DslInteractionEvent as H, RuntimeEvents as I, RuntimeEventName as J, RuntimeEmit as K, RuntimeOptions as R, RenderTree as a, DslView as b, DslStyle as c, DslViewType as d, DslNodeType as e, DslLength as f, DslRect as g, DslAnimate as h, DslKeyframe as i, DslActionBase as j, DslNavigateAction as k, DslCloseAction as l, DslCloseAllAction as m, DslOpenAction as n, DslSetStateAction as o, DslTrackAction as p, DslCallAction as q, DslSequenceAction as r, DslAction as s, DslNode as t, DslClosePosition as u, DslCloseButton as v, DslStage as w, DslSourceRef as x, DslAutoplay as y, DslDerived as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaoxiu/marketing-dsl",
3
- "version": "2.8.0",
3
+ "version": "2.9.0",
4
4
  "type": "module",
5
5
  "description": "营销弹窗 DSL 解释器核心,纯逻辑无框架依赖",
6
6
  "license": "MIT",