@yaoxiu/marketing-dsl 2.0.0 → 2.1.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.
@@ -759,6 +759,7 @@ function validateView(view, prefix, ctx) {
759
759
  if (["popup", "banner", "notice"].indexOf(view.type) === -1) {
760
760
  add(at("type"), "type \u5FC5\u987B\u662F popup / banner / notice \u4E4B\u4E00");
761
761
  }
762
+ ctx.viewType = view.type;
762
763
  validateStage(
763
764
  view.stage,
764
765
  (path, message) => add(prefix ? `${prefix}.${path}` : path, message),
@@ -782,6 +783,16 @@ function checkLength(value) {
782
783
  if (isLength(value)) return true;
783
784
  return value.trim().split(/\s+/).every(isLength);
784
785
  }
786
+ var BANNER_MIN_ASPECT_RATIO = 12;
787
+ function parseAspectRatio(value) {
788
+ if (typeof value === "number") return isFinite(value) && value > 0 ? value : null;
789
+ if (typeof value !== "string" || value.indexOf("{{") > -1) return null;
790
+ const parts = value.split("/");
791
+ const width = Number(parts[0]);
792
+ const height = parts.length > 1 ? Number(parts[1]) : 1;
793
+ if (!isFinite(width) || !isFinite(height) || width <= 0 || height <= 0) return null;
794
+ return width / height;
795
+ }
785
796
  function checkAspectRatio(value) {
786
797
  if (typeof value === "string" && value.indexOf("{{") > -1) return true;
787
798
  if (typeof value === "number") return isFinite(value) && value > 0;
@@ -949,11 +960,21 @@ function validateNode(node, path, layout, ctx) {
949
960
  `"${node.style[key]}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6\uFF0C\u652F\u6301\u6570\u5B57(px) / '100%' / 'auto' \u7B49`
950
961
  );
951
962
  }
952
- if (key === "aspectRatio" && !checkAspectRatio(node.style[key])) {
953
- add(
954
- `${path}.style.aspectRatio`,
955
- "aspectRatio \u8981\u5199\u6210 '750 / 200' \u6216 1.5 \u8FD9\u6837\u7684\u5BBD\u9AD8\u6BD4"
956
- );
963
+ if (key === "aspectRatio") {
964
+ if (!checkAspectRatio(node.style[key])) {
965
+ add(
966
+ `${path}.style.aspectRatio`,
967
+ "aspectRatio \u8981\u5199\u6210 '750 / 200' \u6216 1.5 \u8FD9\u6837\u7684\u5BBD\u9AD8\u6BD4"
968
+ );
969
+ } else if (ctx.viewType === "banner") {
970
+ const ratio = parseAspectRatio(node.style[key]);
971
+ if (ratio !== null && ratio < BANNER_MIN_ASPECT_RATIO) {
972
+ add(
973
+ `${path}.style.aspectRatio`,
974
+ `banner \u7684\u5BBD\u9AD8\u6BD4\u4E0D\u80FD\u5C0F\u4E8E ${BANNER_MIN_ASPECT_RATIO}:1\uFF08\u5F53\u524D\u7EA6 ${ratio.toFixed(1)}:1\uFF09\u3002banner \u5751\u4F4D\u5E38\u9A7B\u9875\u9762\u9876\u90E8\uFF0C\u6BD4\u4F8B\u8D8A\u65B9\u5728\u5BBD\u5C4F\u4E0A\u8D8A\u9AD8 \u2014\u2014 1800px \u5BBD\u7684\u5751\u4F4D\u91CC\uFF0C${ratio.toFixed(1)}:1 \u4F1A\u7B97\u51FA ${Math.round(1800 / ratio)}px \u9AD8\uFF0C\u5403\u6389\u9996\u5C4F\u4E00\u5927\u5757`
975
+ );
976
+ }
977
+ }
957
978
  }
958
979
  });
959
980
  }
@@ -1084,4 +1105,4 @@ function formatIssues(issues) {
1084
1105
  return issues.map((item) => item.path ? `${item.path}: ${item.message}` : item.message).join("\n");
1085
1106
  }
1086
1107
 
1087
- export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, DEFAULT_USER_FIELDS, DSL_VERSION, NODE_TYPES, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, check, computeParts, createTicker, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, resolveNodeStyle, toCssStyle, toLength, validate, validateUserFields };
1108
+ export { ACTION_TYPES, ALLOWED_STYLE_KEYS, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, DEFAULT_USER_FIELDS, DSL_VERSION, NODE_TYPES, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, check, computeParts, createTicker, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, resolveNodeStyle, toCssStyle, toLength, validate, validateUserFields };
@@ -1090,6 +1090,11 @@ var sections6 = [
1090
1090
  t: "p",
1091
1091
  text: "\u5706\u89D2\u88C1\u5207\u53EA\u4F5C\u7528\u4E8E\u5F39\u7A97\u5185\u5BB9\uFF0C\u5173\u95ED\u6309\u94AE\u5728\u88C1\u5207\u5C42\u4E4B\u5916\uFF0C\u6240\u4EE5 `offset` \u5199\u8D1F\u503C\u65F6\u4E0D\u4F1A\u88AB\u88C1\u6389\u3002\u4E24\u79CD\u5E38\u89C1\u505A\u6CD5\uFF1A"
1092
1092
  },
1093
+ {
1094
+ t: "note",
1095
+ tone: "warn",
1096
+ text: "**\u4E24\u4E2A\u503C\u8981\u540C\u5411**\uFF1A`[x, y]` \u8981\u4E48\u90FD\u662F\u6B63\u6570\uFF08\u8D34\u5728\u5F39\u7A97\u5185\u4FA7\uFF09\uFF0C\u8981\u4E48\u90FD\u662F\u8D1F\u6570\uFF08\u6311\u5230\u5F39\u7A97\u5916\u4FA7\uFF09\u3002\u5199\u6210 `[-12, 12]` \u8FD9\u79CD\u4E00\u8D1F\u4E00\u6B63\uFF0C\u6309\u94AE\u4F1A\u6A2A\u8DE8\u5F39\u7A97\u8FB9\u7F18\u2014\u2014\u6A2A\u5411\u6311\u51FA\u53BB\u4E00\u534A\u3001\u7EB5\u5411\u53C8\u7F29\u5728\u91CC\u9762\uFF0C\u770B\u7740\u5C31\u662F\u8D34\u6B6A\u4E86\u3002"
1097
+ },
1093
1098
  {
1094
1099
  t: "code",
1095
1100
  code: `// \u53F3\u4E0A\u89D2\u5916\u4FA7
@@ -1110,6 +1115,25 @@ var sections6 = [
1110
1115
  tone: "warn",
1111
1116
  text: "\u653E\u5230\u5916\u9762\u65F6\u8BB0\u5F97\u7559\u51FA\u7A7A\u95F4\uFF1A\u5F39\u7A97\u56DB\u5468\u6709 20px \u5185\u8FB9\u8DDD\uFF0C\u8D85\u8FC7\u8FD9\u4E2A\u8DDD\u79BB\u5728\u5C0F\u5C4F\u4E0A\u53EF\u80FD\u8D34\u8FB9\u3002\u5728\u8C03\u8BD5\u53F0\u91CC\u628A\u9884\u89C8\u7F29\u653E\u5207\u5230 100% \u786E\u8BA4\u4E00\u4E0B\u3002"
1112
1117
  },
1118
+ { t: "h3", text: "banner \u7684\u5BBD\u9AD8\u6BD4\uFF1A\u4E0D\u80FD\u5C0F\u4E8E 12:1" },
1119
+ {
1120
+ t: "p",
1121
+ text: "\u6574\u56FE banner \u9760\u6839\u8282\u70B9\u7684 `aspectRatio` \u7EF4\u6301\u7B49\u6BD4\uFF08\u5199\u6B7B\u9AD8\u5EA6\u7684\u8BDD\u5751\u4F4D\u4E00\u5BBD\uFF0C\u56FE\u5C31\u88AB\u6A2A\u5411\u62C9\u53D8\u5F62\uFF09\u3002\u4F46\u6BD4\u4F8B\u8D8A\u65B9\uFF0C\u5728\u5BBD\u5C4F\u4E0A\u7B97\u51FA\u6765\u8D8A\u9AD8\u2014\u2014**\u6BD4\u4F8B\u5C0F\u4E8E 12:1 \u7684\u914D\u7F6E\u4FDD\u5B58\u65F6\u4F1A\u88AB\u62E6\u4E0B**\u3002"
1122
+ },
1123
+ {
1124
+ t: "table",
1125
+ head: ["\u5BBD\u9AD8\u6BD4", "1800px \u5BBD\u7684\u5751\u4F4D\u91CC\u6709\u591A\u9AD8", "\u7ED3\u679C"],
1126
+ rows: [
1127
+ ["`960 / 56`\uFF08\u224817:1\uFF09", "\u7EA6 105px", "\u2705 \u4E00\u6761\u901A\u680F\u63D0\u793A"],
1128
+ ["`24 / 1`", "\u7EA6 75px", "\u2705 \u66F4\u6241\uFF0C\u89C6\u89C9\u66F4\u8F7B"],
1129
+ ["`1200 / 300`\uFF084:1\uFF09", "\u7EA6 451px", "\u274C \u5403\u6389\u9996\u5C4F\u4E00\u5927\u5757"]
1130
+ ]
1131
+ },
1132
+ {
1133
+ t: "note",
1134
+ tone: "info",
1135
+ text: "banner \u5751\u4F4D\u5E38\u9A7B\u5728\u9875\u9762\u9876\u90E8\u3001\u4E0D\u8DDF\u968F\u5185\u5BB9\u6EDA\u52A8\uFF0C\u9AD8\u5EA6\u662F**\u5751\u4F4D\u7684\u8BBE\u8BA1\u5C3A\u5BF8**\uFF0C\u4E0D\u662F\u914D\u7F6E\u7684\u81EA\u7531\u5EA6\u3002\u9700\u8981\u5927\u56FE\u5F3A\u66DD\u5149\uFF0C\u7528\u5F39\u7A97\uFF08popup\uFF09\uFF0C\u522B\u628A banner \u6491\u6210\u5927\u56FE\u3002"
1136
+ },
1113
1137
  { t: "h3", text: "banner / \u516C\u544A\u6761\uFF1A\u522B\u5199 maxWidth" },
1114
1138
  {
1115
1139
  t: "p",
@@ -1477,7 +1501,10 @@ var changelog = [
1477
1501
  "\u3010\u5DF2\u77E5\u7A7A\u767D\u3011\u591A\u89C6\u56FE\u91CC\u300C\u5173\u6389\u67D0\u4E00\u5C42\u300D\u4E0D\u4EA7\u751F\u4EFB\u4F55\u4E0A\u62A5\uFF08\u5982\u516C\u544A\u6761\u5F39\u51FA\u5F39\u7A97\u540E\u53C8\u5173\u6389\u5F39\u7A97\uFF0C\u7269\u6599\u5E76\u6CA1\u6709\u5173\uFF09\uFF0C\u90A3\u4E00\u5C42\u914D\u7684 onClose \u57CB\u70B9\u7B49\u4E0D\u5230\u6570\u636E",
1478
1502
  "banner / \u516C\u544A\u6761\u4E0D\u8981\u5199 maxWidth\uFF08\u7B2C 3 \u8282\uFF09\uFF1A\u901A\u680F\u5F62\u6001\u7684\u5BBD\u5EA6\u8BE5\u7531\u9875\u9762\u5751\u4F4D\u51B3\u5B9A\uFF0C\u5199\u6B7B\u4E0A\u9650\u4F1A\u94FA\u4E0D\u6EE1\u3001\u53F3\u8FB9\u7A7A\u4E00\u622A\uFF1B\u786E\u5B9E\u8981\u9650\u5BBD\u5219\u4F1A\u81EA\u52A8\u5C45\u4E2D",
1479
1503
  "\u6570\u636E\u6E90\u586B\u4E86\u5BBF\u4E3B\u6CA1\u6CE8\u518C\u7684\u540D\u5B57\uFF0C\u6574\u4EFD\u7269\u6599\u4E00\u76F4\u662F\u7A7A\u7684\u3001\u4E0D\u62A5\u9519\uFF08\u7B2C 8 \u8282\uFF09\uFF1A\u6570\u636E\u6E90\u6309\u9875\u9762\u6CE8\u518C\uFF0C\u8C03\u8BD5\u53F0\u6B63\u5E38\u4E0D\u4EE3\u8868\u76EE\u6807\u9875\u9762\u6B63\u5E38\uFF0C\u914D\u5B8C\u8981\u5728\u771F\u5B9E\u9875\u9762\u8DD1\u4E00\u904D",
1480
- "\u793A\u4F8B \u2464 \u516C\u544A\u6761\u6539\u6210\u53EA\u6709\u53F3\u4FA7\u6309\u94AE\u80FD\u6253\u5F00\u5F39\u7A97\uFF1A\u6574\u6761\u53EF\u70B9\u4F1A\u8BA9\u7528\u6237\u60F3\u9009\u4E2D\u6587\u6848\u590D\u5236\u65F6\u8BEF\u89E6\u53D1"
1504
+ "\u793A\u4F8B \u2464 \u516C\u544A\u6761\u6539\u6210\u53EA\u6709\u53F3\u4FA7\u6309\u94AE\u80FD\u6253\u5F00\u5F39\u7A97\uFF1A\u6574\u6761\u53EF\u70B9\u4F1A\u8BA9\u7528\u6237\u60F3\u9009\u4E2D\u6587\u6848\u590D\u5236\u65F6\u8BEF\u89E6\u53D1",
1505
+ "\u3010\u65B0\u589E\u786C\u7EA6\u675F\u3011banner \u7684\u5BBD\u9AD8\u6BD4\u4E0D\u80FD\u5C0F\u4E8E 12:1\uFF08\u7B2C 3 \u8282\uFF09\uFF1Abanner \u9AD8\u5EA6\u662F\u300C\u5751\u4F4D\u5BBD\u5EA6 \xD7 \u6BD4\u4F8B\u300D\u7B97\u51FA\u6765\u7684\uFF0C\u6BD4\u4F8B\u8D8A\u65B9\u5728\u5BBD\u5C4F\u4E0A\u8D8A\u9AD8\uFF0C1200\xD7300 \u90A3\u79CD\u5927\u56FE\u5728 1800 \u5BBD\u7684\u5751\u4F4D\u91CC\u6709 451px\u3001\u76F4\u63A5\u5403\u6389\u9996\u5C4F\uFF0C\u73B0\u5728\u4FDD\u5B58\u65F6\u4F1A\u88AB\u62E6\u4E0B",
1506
+ "\u5173\u95ED\u6309\u94AE\u7684 offset \u4E24\u4E2A\u503C\u8981\u540C\u5411\uFF08\u7B2C 3 \u8282\uFF09\uFF1A\u5199\u6210 [-12, 12] \u8FD9\u79CD\u4E00\u8D1F\u4E00\u6B63\uFF0C\u6309\u94AE\u4F1A\u6A2A\u8DE8\u5F39\u7A97\u8FB9\u7F18\u3001\u770B\u7740\u50CF\u8D34\u6B6A\u4E86\uFF0C\u793A\u4F8B \u2466 \u5DF2\u4FEE",
1507
+ "\u793A\u4F8B \u2467 \u6574\u56FE Banner \u7684\u5173\u95ED\u56FE\u6807\u6539\u7528 calc(100% - 32px) \u56FA\u5B9A\u8D34\u53F3\u2014\u2014\u539F\u5148\u7528\u767E\u5206\u6BD4\u5B9A\u4F4D\uFF0C\u5751\u4F4D\u8D8A\u5BBD\u79BB\u53F3\u8FB9\u7F18\u8D8A\u8FDC\uFF081885 \u5BBD\u65F6\u5DEE\u51FA\u5FEB 200px\uFF09\uFF0C\u770B\u7740\u50CF\u98D8\u5728\u4E2D\u95F4"
1481
1508
  ]
1482
1509
  },
1483
1510
  {
@@ -2908,7 +2935,9 @@ var tabTheme = {
2908
2935
  closeButton: {
2909
2936
  show: true,
2910
2937
  position: "top-right",
2911
- offset: [-12, 12],
2938
+ // 两个值要么都往内、要么都往外:[-12, 12] 这种一外一内会让按钮横跨弹窗边缘,
2939
+ // 看着像贴歪了。这里放在弹窗内侧右上角(stage 有 22 的 padding,压不到内容)
2940
+ offset: [12, 12],
2912
2941
  size: 28,
2913
2942
  icon: "\u2715",
2914
2943
  style: { color: "rgba(255,255,255,0.85)", fontSize: 16 }
@@ -3070,18 +3099,18 @@ var tabTheme = {
3070
3099
  var tab_theme_default = tabTheme;
3071
3100
 
3072
3101
  // src/docs/examples/image-banner.ts
3073
- var RATIO = "960 / 56";
3074
- var BANNER_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="960" height="56" viewBox="0 0 960 56">
3102
+ var RATIO = "1920 / 75";
3103
+ var BANNER_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="75" viewBox="0 0 1920 75">
3075
3104
  <defs>
3076
3105
  <linearGradient id="g" x1="0" y1="0" x2="1" y2="0">
3077
3106
  <stop offset="0" stop-color="#ff4433"/>
3078
3107
  <stop offset="1" stop-color="#ff8a3c"/>
3079
3108
  </linearGradient>
3080
3109
  </defs>
3081
- <rect width="960" height="56" fill="url(#g)"/>
3082
- <text x="24" y="34" font-family="sans-serif" font-size="15" fill="#ffffff">\u79CB\u5B63\u5927\u4FC3 \xB7 \u6574\u56FE Banner \u5360\u4F4D\u56FE\uFF08\u5BBD\u5EA6\u81EA\u9002\u5E94\uFF09</text>
3083
- <rect x="749" y="12" width="144" height="32" rx="16" fill="#ffffff" fill-opacity="0.92"/>
3084
- <text x="821" y="33" font-family="sans-serif" font-size="14" fill="#d43a1f" text-anchor="middle">\u7ACB\u5373\u67E5\u770B</text>
3110
+ <rect width="1920" height="75" fill="url(#g)"/>
3111
+ <text x="48" y="47" font-family="sans-serif" font-size="24" fill="#ffffff">\u79CB\u5B63\u5927\u4FC3 \xB7 \u6574\u56FE Banner \u5360\u4F4D\u56FE</text>
3112
+ <rect x="1344" y="19" width="288" height="38" rx="19" fill="#ffffff" fill-opacity="0.92"/>
3113
+ <text x="1488" y="45" font-family="sans-serif" font-size="22" fill="#d43a1f" text-anchor="middle">\u7ACB\u5373\u67E5\u770B</text>
3085
3114
  </svg>`;
3086
3115
  var BANNER_IMAGE = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(BANNER_SVG)}`;
3087
3116
  var imageBanner = {
@@ -3090,11 +3119,11 @@ var imageBanner = {
3090
3119
  meta: { name: "\u6574\u56FE Banner\uFF08\u7A84\u901A\u680F + \u70ED\u533A\uFF09" },
3091
3120
  stage: {
3092
3121
  width: "100%",
3122
+ // 高度由下面那层 box 的 aspectRatio 撑出来,所以这里是 auto + flow
3093
3123
  height: "auto",
3094
3124
  layout: "flow",
3095
- // 不写 maxWidth:banner 是通栏类型,宽度该由宿主坑位决定。写死上限后一旦
3096
- // 小于坑位宽度就铺不满、右边留白。占位图是矢量 SVG + objectFit:cover,
3097
- // 拉到多宽都不会糊;高度由下面的 aspectRatio 按实际宽度算
3125
+ // 不写 maxWidth:banner 是通栏类型,宽度该由宿主坑位决定。
3126
+ // 写死上限后一旦小于坑位宽度就铺不满、右边留白
3098
3127
  style: { radius: 8, overflow: "hidden" },
3099
3128
  onClose: {
3100
3129
  type: "track",
@@ -3104,7 +3133,8 @@ var imageBanner = {
3104
3133
  },
3105
3134
  nodes: [
3106
3135
  {
3107
- // 热区的定位容器:宽度铺满,高度由 aspectRatio
3136
+ // 热区的定位容器:宽度铺满,高度由 aspectRatio 按实际宽度算。
3137
+ // 子节点都是绝对定位、撑不起高度,这里没有确定高度的话热区的百分比 rect 全算成 0
3108
3138
  type: "box",
3109
3139
  style: { width: "100%", aspectRatio: RATIO },
3110
3140
  children: [
@@ -3112,12 +3142,13 @@ var imageBanner = {
3112
3142
  type: "image",
3113
3143
  rect: [0, 0, "100%", "100%"],
3114
3144
  src: BANNER_IMAGE,
3145
+ // 容器比例与图比例一致,cover 既不裁也不拉,图里元素与百分比热区天然对齐
3115
3146
  style: { objectFit: "cover" }
3116
3147
  },
3117
3148
  {
3118
- // 左半边:文案区,点了看活动详情(1.5% ~ 47.5%,不碰右侧按钮)
3149
+ // 左半边:文案区,点了看活动详情(图里文字从 48/1920 起,热区框大一圈)
3119
3150
  type: "box",
3120
- rect: ["1.5%", "14%", "46%", "72%"],
3151
+ rect: ["1.5%", "16%", "46%", "68%"],
3121
3152
  style: { radius: 6, background: "rgba(255,255,255,0.16)", cursor: "pointer" },
3122
3153
  action: {
3123
3154
  type: "sequence",
@@ -3132,9 +3163,9 @@ var imageBanner = {
3132
3163
  }
3133
3164
  },
3134
3165
  {
3135
- // 右侧按钮:直接去下单(78% ~ 94%,正好盖住图里那颗白色按钮)
3166
+ // 右侧按钮:直接去下单。图里按钮是 1344~1632(70%~85%),热区照着框
3136
3167
  type: "box",
3137
- rect: ["78%", "16%", "16%", "68%"],
3168
+ rect: ["70%", "25%", "15%", "51%"],
3138
3169
  style: { radius: 999, background: "rgba(255,255,255,0.16)", cursor: "pointer" },
3139
3170
  action: {
3140
3171
  type: "sequence",
@@ -3149,10 +3180,13 @@ var imageBanner = {
3149
3180
  }
3150
3181
  },
3151
3182
  {
3152
- // 关闭放在最右侧空白处(96% 起,20px 宽约占 2%),与按钮热区不重叠
3183
+ // 关闭按钮固定贴右:x calc 而不是百分比 ——
3184
+ // 百分比是按容器宽度算的,坑位越宽它离右边缘越远(1885 宽时能差出快 200px),
3185
+ // 看着就像飘在中间。calc(100% - 32px) 让它永远距右边缘 12px(20 宽 + 12 边距);
3186
+ // y 同理用 calc 垂直居中,banner 高度随坑位宽度变,写死百分比会上下飘
3153
3187
  type: "text",
3154
3188
  content: "\u2715",
3155
- rect: ["96%", "25%", 20, 20],
3189
+ rect: ["calc(100% - 32px)", "calc(50% - 10px)", 20, 20],
3156
3190
  style: {
3157
3191
  textAlign: "center",
3158
3192
  lineHeight: 20,
@@ -1,4 +1,4 @@
1
- import { TRACK_EVENT_PATTERN, DEFAULT_USER_FIELDS, USER_FIELD_LABELS, DSL_VERSION, ACTION_TYPES, NODE_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS } from '../chunk-BSJDNS6C.js';
1
+ import { TRACK_EVENT_PATTERN, DEFAULT_USER_FIELDS, USER_FIELD_LABELS, DSL_VERSION, ACTION_TYPES, NODE_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS } from '../chunk-7TWWOK7Q.js';
2
2
 
3
3
  // src/docs/reconcile.ts
4
4
  function pickTokens(rows, columnIndex) {
@@ -965,6 +965,11 @@ var sections6 = [
965
965
  t: "p",
966
966
  text: "\u5706\u89D2\u88C1\u5207\u53EA\u4F5C\u7528\u4E8E\u5F39\u7A97\u5185\u5BB9\uFF0C\u5173\u95ED\u6309\u94AE\u5728\u88C1\u5207\u5C42\u4E4B\u5916\uFF0C\u6240\u4EE5 `offset` \u5199\u8D1F\u503C\u65F6\u4E0D\u4F1A\u88AB\u88C1\u6389\u3002\u4E24\u79CD\u5E38\u89C1\u505A\u6CD5\uFF1A"
967
967
  },
968
+ {
969
+ t: "note",
970
+ tone: "warn",
971
+ text: "**\u4E24\u4E2A\u503C\u8981\u540C\u5411**\uFF1A`[x, y]` \u8981\u4E48\u90FD\u662F\u6B63\u6570\uFF08\u8D34\u5728\u5F39\u7A97\u5185\u4FA7\uFF09\uFF0C\u8981\u4E48\u90FD\u662F\u8D1F\u6570\uFF08\u6311\u5230\u5F39\u7A97\u5916\u4FA7\uFF09\u3002\u5199\u6210 `[-12, 12]` \u8FD9\u79CD\u4E00\u8D1F\u4E00\u6B63\uFF0C\u6309\u94AE\u4F1A\u6A2A\u8DE8\u5F39\u7A97\u8FB9\u7F18\u2014\u2014\u6A2A\u5411\u6311\u51FA\u53BB\u4E00\u534A\u3001\u7EB5\u5411\u53C8\u7F29\u5728\u91CC\u9762\uFF0C\u770B\u7740\u5C31\u662F\u8D34\u6B6A\u4E86\u3002"
972
+ },
968
973
  {
969
974
  t: "code",
970
975
  code: `// \u53F3\u4E0A\u89D2\u5916\u4FA7
@@ -985,6 +990,25 @@ var sections6 = [
985
990
  tone: "warn",
986
991
  text: "\u653E\u5230\u5916\u9762\u65F6\u8BB0\u5F97\u7559\u51FA\u7A7A\u95F4\uFF1A\u5F39\u7A97\u56DB\u5468\u6709 20px \u5185\u8FB9\u8DDD\uFF0C\u8D85\u8FC7\u8FD9\u4E2A\u8DDD\u79BB\u5728\u5C0F\u5C4F\u4E0A\u53EF\u80FD\u8D34\u8FB9\u3002\u5728\u8C03\u8BD5\u53F0\u91CC\u628A\u9884\u89C8\u7F29\u653E\u5207\u5230 100% \u786E\u8BA4\u4E00\u4E0B\u3002"
987
992
  },
993
+ { t: "h3", text: "banner \u7684\u5BBD\u9AD8\u6BD4\uFF1A\u4E0D\u80FD\u5C0F\u4E8E 12:1" },
994
+ {
995
+ t: "p",
996
+ text: "\u6574\u56FE banner \u9760\u6839\u8282\u70B9\u7684 `aspectRatio` \u7EF4\u6301\u7B49\u6BD4\uFF08\u5199\u6B7B\u9AD8\u5EA6\u7684\u8BDD\u5751\u4F4D\u4E00\u5BBD\uFF0C\u56FE\u5C31\u88AB\u6A2A\u5411\u62C9\u53D8\u5F62\uFF09\u3002\u4F46\u6BD4\u4F8B\u8D8A\u65B9\uFF0C\u5728\u5BBD\u5C4F\u4E0A\u7B97\u51FA\u6765\u8D8A\u9AD8\u2014\u2014**\u6BD4\u4F8B\u5C0F\u4E8E 12:1 \u7684\u914D\u7F6E\u4FDD\u5B58\u65F6\u4F1A\u88AB\u62E6\u4E0B**\u3002"
997
+ },
998
+ {
999
+ t: "table",
1000
+ head: ["\u5BBD\u9AD8\u6BD4", "1800px \u5BBD\u7684\u5751\u4F4D\u91CC\u6709\u591A\u9AD8", "\u7ED3\u679C"],
1001
+ rows: [
1002
+ ["`960 / 56`\uFF08\u224817:1\uFF09", "\u7EA6 105px", "\u2705 \u4E00\u6761\u901A\u680F\u63D0\u793A"],
1003
+ ["`24 / 1`", "\u7EA6 75px", "\u2705 \u66F4\u6241\uFF0C\u89C6\u89C9\u66F4\u8F7B"],
1004
+ ["`1200 / 300`\uFF084:1\uFF09", "\u7EA6 451px", "\u274C \u5403\u6389\u9996\u5C4F\u4E00\u5927\u5757"]
1005
+ ]
1006
+ },
1007
+ {
1008
+ t: "note",
1009
+ tone: "info",
1010
+ text: "banner \u5751\u4F4D\u5E38\u9A7B\u5728\u9875\u9762\u9876\u90E8\u3001\u4E0D\u8DDF\u968F\u5185\u5BB9\u6EDA\u52A8\uFF0C\u9AD8\u5EA6\u662F**\u5751\u4F4D\u7684\u8BBE\u8BA1\u5C3A\u5BF8**\uFF0C\u4E0D\u662F\u914D\u7F6E\u7684\u81EA\u7531\u5EA6\u3002\u9700\u8981\u5927\u56FE\u5F3A\u66DD\u5149\uFF0C\u7528\u5F39\u7A97\uFF08popup\uFF09\uFF0C\u522B\u628A banner \u6491\u6210\u5927\u56FE\u3002"
1011
+ },
988
1012
  { t: "h3", text: "banner / \u516C\u544A\u6761\uFF1A\u522B\u5199 maxWidth" },
989
1013
  {
990
1014
  t: "p",
@@ -1352,7 +1376,10 @@ var changelog = [
1352
1376
  "\u3010\u5DF2\u77E5\u7A7A\u767D\u3011\u591A\u89C6\u56FE\u91CC\u300C\u5173\u6389\u67D0\u4E00\u5C42\u300D\u4E0D\u4EA7\u751F\u4EFB\u4F55\u4E0A\u62A5\uFF08\u5982\u516C\u544A\u6761\u5F39\u51FA\u5F39\u7A97\u540E\u53C8\u5173\u6389\u5F39\u7A97\uFF0C\u7269\u6599\u5E76\u6CA1\u6709\u5173\uFF09\uFF0C\u90A3\u4E00\u5C42\u914D\u7684 onClose \u57CB\u70B9\u7B49\u4E0D\u5230\u6570\u636E",
1353
1377
  "banner / \u516C\u544A\u6761\u4E0D\u8981\u5199 maxWidth\uFF08\u7B2C 3 \u8282\uFF09\uFF1A\u901A\u680F\u5F62\u6001\u7684\u5BBD\u5EA6\u8BE5\u7531\u9875\u9762\u5751\u4F4D\u51B3\u5B9A\uFF0C\u5199\u6B7B\u4E0A\u9650\u4F1A\u94FA\u4E0D\u6EE1\u3001\u53F3\u8FB9\u7A7A\u4E00\u622A\uFF1B\u786E\u5B9E\u8981\u9650\u5BBD\u5219\u4F1A\u81EA\u52A8\u5C45\u4E2D",
1354
1378
  "\u6570\u636E\u6E90\u586B\u4E86\u5BBF\u4E3B\u6CA1\u6CE8\u518C\u7684\u540D\u5B57\uFF0C\u6574\u4EFD\u7269\u6599\u4E00\u76F4\u662F\u7A7A\u7684\u3001\u4E0D\u62A5\u9519\uFF08\u7B2C 8 \u8282\uFF09\uFF1A\u6570\u636E\u6E90\u6309\u9875\u9762\u6CE8\u518C\uFF0C\u8C03\u8BD5\u53F0\u6B63\u5E38\u4E0D\u4EE3\u8868\u76EE\u6807\u9875\u9762\u6B63\u5E38\uFF0C\u914D\u5B8C\u8981\u5728\u771F\u5B9E\u9875\u9762\u8DD1\u4E00\u904D",
1355
- "\u793A\u4F8B \u2464 \u516C\u544A\u6761\u6539\u6210\u53EA\u6709\u53F3\u4FA7\u6309\u94AE\u80FD\u6253\u5F00\u5F39\u7A97\uFF1A\u6574\u6761\u53EF\u70B9\u4F1A\u8BA9\u7528\u6237\u60F3\u9009\u4E2D\u6587\u6848\u590D\u5236\u65F6\u8BEF\u89E6\u53D1"
1379
+ "\u793A\u4F8B \u2464 \u516C\u544A\u6761\u6539\u6210\u53EA\u6709\u53F3\u4FA7\u6309\u94AE\u80FD\u6253\u5F00\u5F39\u7A97\uFF1A\u6574\u6761\u53EF\u70B9\u4F1A\u8BA9\u7528\u6237\u60F3\u9009\u4E2D\u6587\u6848\u590D\u5236\u65F6\u8BEF\u89E6\u53D1",
1380
+ "\u3010\u65B0\u589E\u786C\u7EA6\u675F\u3011banner \u7684\u5BBD\u9AD8\u6BD4\u4E0D\u80FD\u5C0F\u4E8E 12:1\uFF08\u7B2C 3 \u8282\uFF09\uFF1Abanner \u9AD8\u5EA6\u662F\u300C\u5751\u4F4D\u5BBD\u5EA6 \xD7 \u6BD4\u4F8B\u300D\u7B97\u51FA\u6765\u7684\uFF0C\u6BD4\u4F8B\u8D8A\u65B9\u5728\u5BBD\u5C4F\u4E0A\u8D8A\u9AD8\uFF0C1200\xD7300 \u90A3\u79CD\u5927\u56FE\u5728 1800 \u5BBD\u7684\u5751\u4F4D\u91CC\u6709 451px\u3001\u76F4\u63A5\u5403\u6389\u9996\u5C4F\uFF0C\u73B0\u5728\u4FDD\u5B58\u65F6\u4F1A\u88AB\u62E6\u4E0B",
1381
+ "\u5173\u95ED\u6309\u94AE\u7684 offset \u4E24\u4E2A\u503C\u8981\u540C\u5411\uFF08\u7B2C 3 \u8282\uFF09\uFF1A\u5199\u6210 [-12, 12] \u8FD9\u79CD\u4E00\u8D1F\u4E00\u6B63\uFF0C\u6309\u94AE\u4F1A\u6A2A\u8DE8\u5F39\u7A97\u8FB9\u7F18\u3001\u770B\u7740\u50CF\u8D34\u6B6A\u4E86\uFF0C\u793A\u4F8B \u2466 \u5DF2\u4FEE",
1382
+ "\u793A\u4F8B \u2467 \u6574\u56FE Banner \u7684\u5173\u95ED\u56FE\u6807\u6539\u7528 calc(100% - 32px) \u56FA\u5B9A\u8D34\u53F3\u2014\u2014\u539F\u5148\u7528\u767E\u5206\u6BD4\u5B9A\u4F4D\uFF0C\u5751\u4F4D\u8D8A\u5BBD\u79BB\u53F3\u8FB9\u7F18\u8D8A\u8FDC\uFF081885 \u5BBD\u65F6\u5DEE\u51FA\u5FEB 200px\uFF09\uFF0C\u770B\u7740\u50CF\u98D8\u5728\u4E2D\u95F4"
1356
1383
  ]
1357
1384
  },
1358
1385
  {
@@ -2783,7 +2810,9 @@ var tabTheme = {
2783
2810
  closeButton: {
2784
2811
  show: true,
2785
2812
  position: "top-right",
2786
- offset: [-12, 12],
2813
+ // 两个值要么都往内、要么都往外:[-12, 12] 这种一外一内会让按钮横跨弹窗边缘,
2814
+ // 看着像贴歪了。这里放在弹窗内侧右上角(stage 有 22 的 padding,压不到内容)
2815
+ offset: [12, 12],
2787
2816
  size: 28,
2788
2817
  icon: "\u2715",
2789
2818
  style: { color: "rgba(255,255,255,0.85)", fontSize: 16 }
@@ -2945,18 +2974,18 @@ var tabTheme = {
2945
2974
  var tab_theme_default = tabTheme;
2946
2975
 
2947
2976
  // src/docs/examples/image-banner.ts
2948
- var RATIO = "960 / 56";
2949
- var BANNER_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="960" height="56" viewBox="0 0 960 56">
2977
+ var RATIO = "1920 / 75";
2978
+ var BANNER_SVG = `<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="75" viewBox="0 0 1920 75">
2950
2979
  <defs>
2951
2980
  <linearGradient id="g" x1="0" y1="0" x2="1" y2="0">
2952
2981
  <stop offset="0" stop-color="#ff4433"/>
2953
2982
  <stop offset="1" stop-color="#ff8a3c"/>
2954
2983
  </linearGradient>
2955
2984
  </defs>
2956
- <rect width="960" height="56" fill="url(#g)"/>
2957
- <text x="24" y="34" font-family="sans-serif" font-size="15" fill="#ffffff">\u79CB\u5B63\u5927\u4FC3 \xB7 \u6574\u56FE Banner \u5360\u4F4D\u56FE\uFF08\u5BBD\u5EA6\u81EA\u9002\u5E94\uFF09</text>
2958
- <rect x="749" y="12" width="144" height="32" rx="16" fill="#ffffff" fill-opacity="0.92"/>
2959
- <text x="821" y="33" font-family="sans-serif" font-size="14" fill="#d43a1f" text-anchor="middle">\u7ACB\u5373\u67E5\u770B</text>
2985
+ <rect width="1920" height="75" fill="url(#g)"/>
2986
+ <text x="48" y="47" font-family="sans-serif" font-size="24" fill="#ffffff">\u79CB\u5B63\u5927\u4FC3 \xB7 \u6574\u56FE Banner \u5360\u4F4D\u56FE</text>
2987
+ <rect x="1344" y="19" width="288" height="38" rx="19" fill="#ffffff" fill-opacity="0.92"/>
2988
+ <text x="1488" y="45" font-family="sans-serif" font-size="22" fill="#d43a1f" text-anchor="middle">\u7ACB\u5373\u67E5\u770B</text>
2960
2989
  </svg>`;
2961
2990
  var BANNER_IMAGE = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(BANNER_SVG)}`;
2962
2991
  var imageBanner = {
@@ -2965,11 +2994,11 @@ var imageBanner = {
2965
2994
  meta: { name: "\u6574\u56FE Banner\uFF08\u7A84\u901A\u680F + \u70ED\u533A\uFF09" },
2966
2995
  stage: {
2967
2996
  width: "100%",
2997
+ // 高度由下面那层 box 的 aspectRatio 撑出来,所以这里是 auto + flow
2968
2998
  height: "auto",
2969
2999
  layout: "flow",
2970
- // 不写 maxWidth:banner 是通栏类型,宽度该由宿主坑位决定。写死上限后一旦
2971
- // 小于坑位宽度就铺不满、右边留白。占位图是矢量 SVG + objectFit:cover,
2972
- // 拉到多宽都不会糊;高度由下面的 aspectRatio 按实际宽度算
3000
+ // 不写 maxWidth:banner 是通栏类型,宽度该由宿主坑位决定。
3001
+ // 写死上限后一旦小于坑位宽度就铺不满、右边留白
2973
3002
  style: { radius: 8, overflow: "hidden" },
2974
3003
  onClose: {
2975
3004
  type: "track",
@@ -2979,7 +3008,8 @@ var imageBanner = {
2979
3008
  },
2980
3009
  nodes: [
2981
3010
  {
2982
- // 热区的定位容器:宽度铺满,高度由 aspectRatio
3011
+ // 热区的定位容器:宽度铺满,高度由 aspectRatio 按实际宽度算。
3012
+ // 子节点都是绝对定位、撑不起高度,这里没有确定高度的话热区的百分比 rect 全算成 0
2983
3013
  type: "box",
2984
3014
  style: { width: "100%", aspectRatio: RATIO },
2985
3015
  children: [
@@ -2987,12 +3017,13 @@ var imageBanner = {
2987
3017
  type: "image",
2988
3018
  rect: [0, 0, "100%", "100%"],
2989
3019
  src: BANNER_IMAGE,
3020
+ // 容器比例与图比例一致,cover 既不裁也不拉,图里元素与百分比热区天然对齐
2990
3021
  style: { objectFit: "cover" }
2991
3022
  },
2992
3023
  {
2993
- // 左半边:文案区,点了看活动详情(1.5% ~ 47.5%,不碰右侧按钮)
3024
+ // 左半边:文案区,点了看活动详情(图里文字从 48/1920 起,热区框大一圈)
2994
3025
  type: "box",
2995
- rect: ["1.5%", "14%", "46%", "72%"],
3026
+ rect: ["1.5%", "16%", "46%", "68%"],
2996
3027
  style: { radius: 6, background: "rgba(255,255,255,0.16)", cursor: "pointer" },
2997
3028
  action: {
2998
3029
  type: "sequence",
@@ -3007,9 +3038,9 @@ var imageBanner = {
3007
3038
  }
3008
3039
  },
3009
3040
  {
3010
- // 右侧按钮:直接去下单(78% ~ 94%,正好盖住图里那颗白色按钮)
3041
+ // 右侧按钮:直接去下单。图里按钮是 1344~1632(70%~85%),热区照着框
3011
3042
  type: "box",
3012
- rect: ["78%", "16%", "16%", "68%"],
3043
+ rect: ["70%", "25%", "15%", "51%"],
3013
3044
  style: { radius: 999, background: "rgba(255,255,255,0.16)", cursor: "pointer" },
3014
3045
  action: {
3015
3046
  type: "sequence",
@@ -3024,10 +3055,13 @@ var imageBanner = {
3024
3055
  }
3025
3056
  },
3026
3057
  {
3027
- // 关闭放在最右侧空白处(96% 起,20px 宽约占 2%),与按钮热区不重叠
3058
+ // 关闭按钮固定贴右:x calc 而不是百分比 ——
3059
+ // 百分比是按容器宽度算的,坑位越宽它离右边缘越远(1885 宽时能差出快 200px),
3060
+ // 看着就像飘在中间。calc(100% - 32px) 让它永远距右边缘 12px(20 宽 + 12 边距);
3061
+ // y 同理用 calc 垂直居中,banner 高度随坑位宽度变,写死百分比会上下飘
3028
3062
  type: "text",
3029
3063
  content: "\u2715",
3030
- rect: ["96%", "25%", 20, 20],
3064
+ rect: ["calc(100% - 32px)", "calc(50% - 10px)", 20, 20],
3031
3065
  style: {
3032
3066
  textAlign: "center",
3033
3067
  lineHeight: 20,
package/dist/index.cjs CHANGED
@@ -1528,6 +1528,7 @@ function validateView(view, prefix, ctx) {
1528
1528
  if (["popup", "banner", "notice"].indexOf(view.type) === -1) {
1529
1529
  add(at("type"), "type \u5FC5\u987B\u662F popup / banner / notice \u4E4B\u4E00");
1530
1530
  }
1531
+ ctx.viewType = view.type;
1531
1532
  validateStage(
1532
1533
  view.stage,
1533
1534
  (path, message) => add(prefix ? `${prefix}.${path}` : path, message),
@@ -1551,6 +1552,16 @@ function checkLength(value) {
1551
1552
  if (isLength(value)) return true;
1552
1553
  return value.trim().split(/\s+/).every(isLength);
1553
1554
  }
1555
+ var BANNER_MIN_ASPECT_RATIO = 12;
1556
+ function parseAspectRatio(value) {
1557
+ if (typeof value === "number") return isFinite(value) && value > 0 ? value : null;
1558
+ if (typeof value !== "string" || value.indexOf("{{") > -1) return null;
1559
+ const parts = value.split("/");
1560
+ const width = Number(parts[0]);
1561
+ const height = parts.length > 1 ? Number(parts[1]) : 1;
1562
+ if (!isFinite(width) || !isFinite(height) || width <= 0 || height <= 0) return null;
1563
+ return width / height;
1564
+ }
1554
1565
  function checkAspectRatio(value) {
1555
1566
  if (typeof value === "string" && value.indexOf("{{") > -1) return true;
1556
1567
  if (typeof value === "number") return isFinite(value) && value > 0;
@@ -1718,11 +1729,21 @@ function validateNode(node, path, layout, ctx) {
1718
1729
  `"${node.style[key]}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6\uFF0C\u652F\u6301\u6570\u5B57(px) / '100%' / 'auto' \u7B49`
1719
1730
  );
1720
1731
  }
1721
- if (key === "aspectRatio" && !checkAspectRatio(node.style[key])) {
1722
- add(
1723
- `${path}.style.aspectRatio`,
1724
- "aspectRatio \u8981\u5199\u6210 '750 / 200' \u6216 1.5 \u8FD9\u6837\u7684\u5BBD\u9AD8\u6BD4"
1725
- );
1732
+ if (key === "aspectRatio") {
1733
+ if (!checkAspectRatio(node.style[key])) {
1734
+ add(
1735
+ `${path}.style.aspectRatio`,
1736
+ "aspectRatio \u8981\u5199\u6210 '750 / 200' \u6216 1.5 \u8FD9\u6837\u7684\u5BBD\u9AD8\u6BD4"
1737
+ );
1738
+ } else if (ctx.viewType === "banner") {
1739
+ const ratio = parseAspectRatio(node.style[key]);
1740
+ if (ratio !== null && ratio < BANNER_MIN_ASPECT_RATIO) {
1741
+ add(
1742
+ `${path}.style.aspectRatio`,
1743
+ `banner \u7684\u5BBD\u9AD8\u6BD4\u4E0D\u80FD\u5C0F\u4E8E ${BANNER_MIN_ASPECT_RATIO}:1\uFF08\u5F53\u524D\u7EA6 ${ratio.toFixed(1)}:1\uFF09\u3002banner \u5751\u4F4D\u5E38\u9A7B\u9875\u9762\u9876\u90E8\uFF0C\u6BD4\u4F8B\u8D8A\u65B9\u5728\u5BBD\u5C4F\u4E0A\u8D8A\u9AD8 \u2014\u2014 1800px \u5BBD\u7684\u5751\u4F4D\u91CC\uFF0C${ratio.toFixed(1)}:1 \u4F1A\u7B97\u51FA ${Math.round(1800 / ratio)}px \u9AD8\uFF0C\u5403\u6389\u9996\u5C4F\u4E00\u5927\u5757`
1744
+ );
1745
+ }
1746
+ }
1726
1747
  }
1727
1748
  });
1728
1749
  }
@@ -1855,6 +1876,7 @@ function formatIssues(issues) {
1855
1876
 
1856
1877
  exports.ACTION_TYPES = ACTION_TYPES;
1857
1878
  exports.ALLOWED_STYLE_KEYS = ALLOWED_STYLE_KEYS;
1879
+ exports.BANNER_MIN_ASPECT_RATIO = BANNER_MIN_ASPECT_RATIO;
1858
1880
  exports.CLOSE_POSITIONS = CLOSE_POSITIONS;
1859
1881
  exports.DEFAULT_USER_FIELDS = DEFAULT_USER_FIELDS;
1860
1882
  exports.DSL_VERSION = DSL_VERSION;
package/dist/index.d.cts CHANGED
@@ -80,6 +80,18 @@ declare const CLOSE_POSITIONS: string[];
80
80
  * @param options 可选项;不传时与旧版行为完全一致
81
81
  */
82
82
  declare function validate(dsl: Dsl | unknown, options?: ValidateOptions): ValidateResult;
83
+ /** 宽高比:'750 / 200'、'16/9' 或纯数字 */
84
+ /**
85
+ * banner 允许的最小宽高比(宽 ÷ 高)。
86
+ *
87
+ * banner 坑位常驻页面顶部、不跟随内容滚动,比例越方在宽屏上越高:
88
+ * 同样 1800px 宽的坑位,17:1 算出来是 105px,4:1(1200×300 那种大图)直接 451px,
89
+ * 首屏一眼看过去全是广告。所以比例必须够扁——**这是坑位的设计尺寸,不是配置的自由度**。
90
+ *
91
+ * 约束比例而不是约束固定高度,是因为整图 banner 必须靠 aspectRatio 维持等比:
92
+ * 写死高度的话坑位一宽,图就被横向拉变形了。
93
+ */
94
+ declare const BANNER_MIN_ASPECT_RATIO = 12;
83
95
  /** 把校验结果拼成可以直接展示的文本 */
84
96
  declare function formatIssues(issues: Issue[]): string;
85
97
 
@@ -236,4 +248,4 @@ declare function computeParts(endTime: number, now?: number): CountdownParts;
236
248
  /** 不给 children 时退化成一行文本,占位符 {d} {h} {hAll} {m} {s} {cs} */
237
249
  declare function formatParts(parts: CountdownParts, format?: string): string;
238
250
 
239
- export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, CssStyle, DEFAULT_USER_FIELDS, DSL_VERSION, Dsl, type DslRuntime, DslStyle, DslView, 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, isLength, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate, validateUserFields };
251
+ export { ACTION_TYPES, ALLOWED_STYLE_KEYS, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, CssStyle, DEFAULT_USER_FIELDS, DSL_VERSION, Dsl, type DslRuntime, DslStyle, DslView, 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, isLength, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate, validateUserFields };
package/dist/index.d.ts CHANGED
@@ -80,6 +80,18 @@ declare const CLOSE_POSITIONS: string[];
80
80
  * @param options 可选项;不传时与旧版行为完全一致
81
81
  */
82
82
  declare function validate(dsl: Dsl | unknown, options?: ValidateOptions): ValidateResult;
83
+ /** 宽高比:'750 / 200'、'16/9' 或纯数字 */
84
+ /**
85
+ * banner 允许的最小宽高比(宽 ÷ 高)。
86
+ *
87
+ * banner 坑位常驻页面顶部、不跟随内容滚动,比例越方在宽屏上越高:
88
+ * 同样 1800px 宽的坑位,17:1 算出来是 105px,4:1(1200×300 那种大图)直接 451px,
89
+ * 首屏一眼看过去全是广告。所以比例必须够扁——**这是坑位的设计尺寸,不是配置的自由度**。
90
+ *
91
+ * 约束比例而不是约束固定高度,是因为整图 banner 必须靠 aspectRatio 维持等比:
92
+ * 写死高度的话坑位一宽,图就被横向拉变形了。
93
+ */
94
+ declare const BANNER_MIN_ASPECT_RATIO = 12;
83
95
  /** 把校验结果拼成可以直接展示的文本 */
84
96
  declare function formatIssues(issues: Issue[]): string;
85
97
 
@@ -236,4 +248,4 @@ declare function computeParts(endTime: number, now?: number): CountdownParts;
236
248
  /** 不给 children 时退化成一行文本,占位符 {d} {h} {hAll} {m} {s} {cs} */
237
249
  declare function formatParts(parts: CountdownParts, format?: string): string;
238
250
 
239
- export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, CssStyle, DEFAULT_USER_FIELDS, DSL_VERSION, Dsl, type DslRuntime, DslStyle, DslView, 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, isLength, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate, validateUserFields };
251
+ export { ACTION_TYPES, ALLOWED_STYLE_KEYS, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, CssStyle, DEFAULT_USER_FIELDS, DSL_VERSION, Dsl, type DslRuntime, DslStyle, DslView, 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, isLength, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate, validateUserFields };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { normalizeViews, createTicker, evaluate, resolveNodeStyle, interpolateDeep, interpolate, toLength, toCssStyle, parseEndTime, computeParts, formatParts } from './chunk-BSJDNS6C.js';
2
- export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, DEFAULT_USER_FIELDS, DSL_VERSION, NODE_TYPES, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, check, computeParts, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, toCssStyle, toLength, validate, validateUserFields } from './chunk-BSJDNS6C.js';
1
+ import { normalizeViews, createTicker, evaluate, resolveNodeStyle, interpolateDeep, interpolate, toLength, toCssStyle, parseEndTime, computeParts, formatParts } from './chunk-7TWWOK7Q.js';
2
+ export { ACTION_TYPES, ALLOWED_STYLE_KEYS, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, DEFAULT_USER_FIELDS, DSL_VERSION, NODE_TYPES, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, check, computeParts, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, toCssStyle, toLength, validate, validateUserFields } from './chunk-7TWWOK7Q.js';
3
3
 
4
4
  // src/url.ts
5
5
  var SAFE_PROTOCOLS = ["http:", "https:", "mailto:", "tel:"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaoxiu/marketing-dsl",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "description": "营销弹窗 DSL 解释器核心,纯逻辑无框架依赖",
6
6
  "license": "MIT",