@yaoxiu/marketing-dsl 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -400,10 +400,24 @@ function parseEndTime(to) {
400
400
  if (typeof to === "number") return to;
401
401
  if (to === void 0 || to === null || to === "") return 0;
402
402
  const raw = String(to).trim();
403
+ if (!raw) return 0;
403
404
  if (/^\d+$/.test(raw)) return Number(raw);
404
- const time = new Date(raw.replace(/-/g, "/")).getTime();
405
+ if (/^\d{4}年/.test(raw)) {
406
+ const normalizedZh = raw.replace(/年|月/g, "/").replace(/日/g, " ").replace(/时|点|分/g, ":").replace(/秒/g, "").replace(/:+/g, ":").replace(/\s+/g, " ").replace(/[:\s]+$/, "").replace(/(\s\d{1,2})$/, "$1:00").trim();
407
+ const time2 = new Date(normalizedZh).getTime();
408
+ return isNaN(time2) ? 0 : time2;
409
+ }
410
+ const isIso = /^\d{4}-\d{2}-\d{2}T/.test(raw);
411
+ const normalized = isIso ? raw : raw.replace(/-/g, "/");
412
+ const time = new Date(normalized).getTime();
405
413
  return isNaN(time) ? 0 : time;
406
414
  }
415
+ function isValidEndTime(to) {
416
+ if (typeof to === "number") return isFinite(to) && to > 0;
417
+ if (typeof to !== "string") return false;
418
+ if (to.indexOf("{{") > -1) return true;
419
+ return parseEndTime(to) > 0;
420
+ }
407
421
  function computeParts(endTime, now = Date.now()) {
408
422
  const raw = endTime - now;
409
423
  const remain = raw > 0 ? raw : 0;
@@ -716,11 +730,12 @@ function resolveCloseButton(config, layerName, closeTop) {
716
730
  const offset = config.offset || [8, 8];
717
731
  const size = config.size === void 0 ? 26 : config.size;
718
732
  const position = config.position || "top-right";
733
+ const image = config.image ? safeImageUrl(config.image) : "";
734
+ const iconMode = image ? "image" : config.icon ? "text" : "svg";
719
735
  const style = {
720
736
  position: "absolute",
721
737
  width: toLength(size),
722
738
  height: toLength(size),
723
- lineHeight: toLength(size),
724
739
  display: "flex",
725
740
  alignItems: "center",
726
741
  justifyContent: "center",
@@ -728,12 +743,15 @@ function resolveCloseButton(config, layerName, closeTop) {
728
743
  borderRadius: "50%",
729
744
  background: "rgba(0, 0, 0, 0.35)",
730
745
  color: "#fff",
731
- fontSize: "18px",
732
- textAlign: "center",
733
746
  cursor: "pointer",
734
747
  userSelect: "none",
735
748
  zIndex: "1"
736
749
  };
750
+ if (iconMode !== "svg") {
751
+ style.lineHeight = toLength(size);
752
+ style.fontSize = "18px";
753
+ style.textAlign = "center";
754
+ }
737
755
  if (position.indexOf("bottom") === 0) style.bottom = toLength(offset[1]);
738
756
  else style.top = toLength(offset[1]);
739
757
  if (position.indexOf("center") > -1) {
@@ -746,8 +764,7 @@ function resolveCloseButton(config, layerName, closeTop) {
746
764
  }
747
765
  Object.assign(style, toCssStyle(config.style));
748
766
  const onClick = () => closeTop("close-button");
749
- const image = config.image ? safeImageUrl(config.image) : "";
750
- if (image) {
767
+ if (iconMode === "image") {
751
768
  return {
752
769
  key: `${layerName}-close`,
753
770
  tag: "div",
@@ -770,15 +787,81 @@ function resolveCloseButton(config, layerName, closeTop) {
770
787
  ]
771
788
  };
772
789
  }
790
+ if (iconMode === "text") {
791
+ return {
792
+ key: `${layerName}-close`,
793
+ tag: "div",
794
+ className: "dsl-close",
795
+ style,
796
+ onClick,
797
+ text: config.icon
798
+ };
799
+ }
773
800
  return {
774
801
  key: `${layerName}-close`,
775
802
  tag: "div",
776
803
  className: "dsl-close",
777
804
  style,
778
805
  onClick,
779
- text: config.icon || "\xD7"
806
+ children: [closeIcon(`${layerName}-close-icon`, size)]
807
+ };
808
+ }
809
+ function closeIcon(key, size) {
810
+ const box = clamp(Math.round(toPx(size, 26) * 0.42), 8, 18);
811
+ const stroke = Math.max(1.2, round2(box * 0.15));
812
+ const a = round2(stroke / 2);
813
+ const b = round2(box - stroke / 2);
814
+ const lineAttrs = {
815
+ stroke: "currentColor",
816
+ "stroke-width": String(stroke),
817
+ "stroke-linecap": "round"
818
+ };
819
+ return {
820
+ key,
821
+ tag: "svg",
822
+ className: "dsl-close-icon",
823
+ style: { display: "block" },
824
+ attrs: {
825
+ width: String(box),
826
+ height: String(box),
827
+ viewBox: `0 0 ${box} ${box}`,
828
+ fill: "none",
829
+ "aria-hidden": "true"
830
+ },
831
+ children: [
832
+ {
833
+ key: `${key}-1`,
834
+ tag: "line",
835
+ className: "dsl-close-icon-line",
836
+ style: {},
837
+ attrs: Object.assign(
838
+ { x1: String(a), y1: String(a), x2: String(b), y2: String(b) },
839
+ lineAttrs
840
+ )
841
+ },
842
+ {
843
+ key: `${key}-2`,
844
+ tag: "line",
845
+ className: "dsl-close-icon-line",
846
+ style: {},
847
+ attrs: Object.assign(
848
+ { x1: String(b), y1: String(a), x2: String(a), y2: String(b) },
849
+ lineAttrs
850
+ )
851
+ }
852
+ ]
780
853
  };
781
854
  }
855
+ function toPx(value, fallback) {
856
+ const num = typeof value === "number" ? value : parseFloat(String(value));
857
+ return isFinite(num) && num > 0 ? num : fallback;
858
+ }
859
+ function clamp(value, min, max) {
860
+ return Math.min(max, Math.max(min, value));
861
+ }
862
+ function round2(value) {
863
+ return Math.round(value * 100) / 100;
864
+ }
782
865
  var BASE_STYLE = { boxSizing: "border-box" };
783
866
  var NODE_CLASS = {
784
867
  box: "dsl-box",
@@ -956,10 +1039,13 @@ function resolveCountdown(node, key, style, context, input, countdowns) {
956
1039
  const parts = computeParts(endTime);
957
1040
  countdowns.endTimes.push(endTime);
958
1041
  if (node.precision === "cs") countdowns.precision = "cs";
959
- if (parts.ended && endTime && node.onEnd) {
1042
+ if (endTime && node.onEnd) {
960
1043
  const token = `${key}@${endTime}`;
961
- if (!input.firedEnds.has(token)) {
962
- input.firedEnds.add(token);
1044
+ const status = input.countdownEnds.get(token);
1045
+ if (status === void 0) {
1046
+ input.countdownEnds.set(token, parts.ended ? "done" : "waiting");
1047
+ } else if (status === "waiting" && parts.ended) {
1048
+ input.countdownEnds.set(token, "done");
963
1049
  input.dispatch(node.onEnd, context);
964
1050
  }
965
1051
  }
@@ -1019,7 +1105,7 @@ function createRuntime(dsl, options = {}) {
1019
1105
  let loadFailed = false;
1020
1106
  let destroyed = false;
1021
1107
  const listeners = /* @__PURE__ */ new Set();
1022
- const firedEnds = /* @__PURE__ */ new Set();
1108
+ const countdownEnds = /* @__PURE__ */ new Map();
1023
1109
  const notify = () => {
1024
1110
  if (destroyed) return;
1025
1111
  listeners.forEach((fn) => fn());
@@ -1153,7 +1239,7 @@ function createRuntime(dsl, options = {}) {
1153
1239
  dispatch,
1154
1240
  setState,
1155
1241
  closeTop,
1156
- firedEnds
1242
+ countdownEnds
1157
1243
  });
1158
1244
  ticker.sync(tree.countdownEndTimes, tree.countdownPrecision);
1159
1245
  return tree;
@@ -1221,6 +1307,7 @@ var ACTION_TYPES = [
1221
1307
  "call",
1222
1308
  "sequence"
1223
1309
  ];
1310
+ var TRACK_EVENT_PATTERN = /^[a-z][a-z0-9_]{2,63}$/;
1224
1311
  var CLOSE_POSITIONS = [
1225
1312
  "top-right",
1226
1313
  "top-left",
@@ -1523,7 +1610,14 @@ function validateNodeByType(node, path, ctx) {
1523
1610
  }
1524
1611
  }
1525
1612
  if (node.type === "countdown") {
1526
- if (!node.to) add(`${path}.to`, "countdown \u5FC5\u987B\u63D0\u4F9B to\uFF08\u7ED3\u675F\u65F6\u95F4\uFF09");
1613
+ if (!node.to) {
1614
+ add(`${path}.to`, "countdown \u5FC5\u987B\u63D0\u4F9B to\uFF08\u7ED3\u675F\u65F6\u95F4\uFF09");
1615
+ } else if (!isValidEndTime(node.to)) {
1616
+ add(
1617
+ `${path}.to`,
1618
+ `"${node.to}" \u4E0D\u662F\u80FD\u8BC6\u522B\u7684\u65F6\u95F4\u3002\u53EF\u4EE5\u5199 "2026-09-01 18:00:00" / "2026-09-01" / "2026\u5E749\u67081\u65E5 18\u70B9" / ISO \u683C\u5F0F / \u65F6\u95F4\u6233`
1619
+ );
1620
+ }
1527
1621
  if (node.precision !== void 0 && ["s", "cs"].indexOf(node.precision) === -1) {
1528
1622
  add(`${path}.precision`, "precision \u53EA\u80FD\u662F 's'\uFF08\u6BCF\u79D2\uFF09\u6216 'cs'\uFF08\u5398\u79D2\uFF0C\u9010\u5E27\u5237\u65B0\uFF09");
1529
1623
  }
@@ -1570,8 +1664,15 @@ function validateAction(action, path, ctx) {
1570
1664
  if (action.type === "setState" && typeof action.key !== "string") {
1571
1665
  add(`${path}.key`, "setState \u5FC5\u987B\u63D0\u4F9B key");
1572
1666
  }
1573
- if (action.type === "track" && !action.event) {
1574
- add(`${path}.event`, "track \u5FC5\u987B\u63D0\u4F9B event");
1667
+ if (action.type === "track") {
1668
+ if (!action.event) {
1669
+ add(`${path}.event`, "track \u5FC5\u987B\u63D0\u4F9B event");
1670
+ } else if (typeof action.event !== "string" || !TRACK_EVENT_PATTERN.test(action.event)) {
1671
+ add(
1672
+ `${path}.event`,
1673
+ `\u57CB\u70B9\u4E8B\u4EF6\u540D "${String(action.event)}" \u4E0D\u5408\u89C4\uFF1A\u53EA\u80FD\u7528\u5C0F\u5199\u82F1\u6587\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u4E0B\u5212\u7EBF\uFF0C\u5FC5\u987B\u4EE5\u5C0F\u5199\u5B57\u6BCD\u5F00\u5934\uFF0C\u603B\u957F\u5EA6 3~64 \u4E2A\u5B57\u7B26\uFF08\u4E0D\u80FD\u7528\u4E2D\u6587\u3001\u5927\u5199\u5B57\u6BCD\u3001\u9A7C\u5CF0\u3001\u4E2D\u5212\u7EBF\u6216\u7A7A\u683C\uFF09\u3002\u6B63\u786E\u793A\u4F8B\uFF1Abanner_renew_click`
1674
+ );
1675
+ }
1575
1676
  }
1576
1677
  if (action.type === "call" && !action.name) {
1577
1678
  add(`${path}.name`, "call \u5FC5\u987B\u63D0\u4F9B name\uFF08\u5BBF\u4E3B\u6CE8\u518C\u7684\u65B9\u6CD5\u540D\uFF09");
@@ -1609,6 +1710,7 @@ exports.CLOSE_POSITIONS = CLOSE_POSITIONS;
1609
1710
  exports.DSL_VERSION = DSL_VERSION;
1610
1711
  exports.NODE_TYPES = NODE_TYPES;
1611
1712
  exports.SINGLE_VIEW_NAME = SINGLE_VIEW_NAME;
1713
+ exports.TRACK_EVENT_PATTERN = TRACK_EVENT_PATTERN;
1612
1714
  exports.check = check;
1613
1715
  exports.computeParts = computeParts;
1614
1716
  exports.createRuntime = createRuntime;
@@ -1618,6 +1720,7 @@ exports.formatParts = formatParts;
1618
1720
  exports.interpolate = interpolate;
1619
1721
  exports.interpolateDeep = interpolateDeep;
1620
1722
  exports.isLength = isLength;
1723
+ exports.isValidEndTime = isValidEndTime;
1621
1724
  exports.normalizeViews = normalizeViews;
1622
1725
  exports.parseEndTime = parseEndTime;
1623
1726
  exports.safeImageUrl = safeImageUrl;
package/dist/index.d.cts CHANGED
@@ -1,285 +1,5 @@
1
- /**
2
- * DSL 的类型定义。
3
- *
4
- * 分两组:
5
- * - Dsl* 是运营写的配置,也就是存进数据库的那份 JSON
6
- * - Render* 是 core 算完之后交给框架层的渲染树,纯数据,跟框架无关
7
- *
8
- * 框架层只认 Render*,永远不该去读 Dsl*——一旦读了,解析逻辑就又漏回渲染层了。
9
- */
10
- type DslViewType = 'popup' | 'banner' | 'notice';
11
- type DslNodeType = 'box' | 'flex' | 'repeat' | 'image' | 'text' | 'button' | 'tabs' | 'countdown';
12
- /** 长度:数字按 px,字符串原样交给 CSS,可以带 {{ }} */
13
- type DslLength = number | string;
14
- /** rect: [x, y, width, height],绝对定位容器里用 */
15
- type DslRect = [DslLength, DslLength, DslLength, DslLength];
16
- /** 样式对象,键必须在白名单内,值可以带 {{ }} */
17
- type DslStyle = Record<string, unknown>;
18
- interface DslActionBase {
19
- type: string;
20
- }
21
- interface DslNavigateAction extends DslActionBase {
22
- type: 'navigate';
23
- url: string;
24
- target?: '_self' | '_blank';
25
- }
26
- interface DslCloseAction extends DslActionBase {
27
- type: 'close';
28
- reason?: string;
29
- }
30
- interface DslCloseAllAction extends DslActionBase {
31
- type: 'closeAll';
32
- reason?: string;
33
- }
34
- interface DslOpenAction extends DslActionBase {
35
- type: 'open';
36
- view: string;
37
- mode?: 'stack' | 'replace';
38
- }
39
- interface DslSetStateAction extends DslActionBase {
40
- type: 'setState';
41
- key: string;
42
- value: unknown;
43
- }
44
- interface DslTrackAction extends DslActionBase {
45
- type: 'track';
46
- event: string;
47
- params?: Record<string, unknown>;
48
- }
49
- /** 调用宿主注册的方法,名字必须在 handlers 白名单内 */
50
- interface DslCallAction extends DslActionBase {
51
- type: 'call';
52
- name: string;
53
- params?: Record<string, unknown>;
54
- }
55
- interface DslSequenceAction extends DslActionBase {
56
- type: 'sequence';
57
- actions: DslAction[];
58
- }
59
- type DslAction = DslNavigateAction | DslCloseAction | DslCloseAllAction | DslOpenAction | DslSetStateAction | DslTrackAction | DslCallAction | DslSequenceAction;
60
- interface DslNode {
61
- id?: string;
62
- type: DslNodeType;
63
- rect?: DslRect;
64
- style?: DslStyle;
65
- /** 表达式,为假时整个节点不渲染 */
66
- visibleWhen?: string;
67
- action?: DslAction;
68
- /** box / flex / countdown */
69
- children?: DslNode[];
70
- /** repeat / tabs:数组来源表达式 */
71
- bind?: string;
72
- /** repeat */
73
- template?: DslNode;
74
- itemName?: string;
75
- indexName?: string;
76
- /** tabs */
77
- stateKey?: string;
78
- labelField?: string;
79
- itemStyle?: DslStyle;
80
- activeItemStyle?: DslStyle;
81
- /** image */
82
- src?: string;
83
- /** text */
84
- content?: string;
85
- /** button */
86
- text?: string;
87
- /** countdown */
88
- to?: string;
89
- precision?: 's' | 'cs';
90
- as?: string;
91
- format?: string;
92
- endText?: string;
93
- onEnd?: DslAction;
94
- }
95
- type DslClosePosition = 'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left' | 'bottom-center';
96
- interface DslCloseButton {
97
- show?: boolean;
98
- position?: DslClosePosition;
99
- /** [x, y],负值把按钮移到弹窗外面 */
100
- offset?: [DslLength, DslLength];
101
- size?: DslLength;
102
- icon?: string;
103
- image?: string;
104
- style?: DslStyle;
105
- }
106
- interface DslStage {
107
- width?: DslLength;
108
- height?: DslLength;
109
- /** absolute(默认)子节点用 rect 定位;flow 子节点按 flex 排 */
110
- layout?: 'absolute' | 'flow';
111
- mask?: boolean;
112
- maskClosable?: boolean;
113
- closeButton?: DslCloseButton;
114
- style?: DslStyle;
115
- /** 曝光埋点:数据就绪、视图真正展示时触发一次 */
116
- onShow?: DslAction;
117
- /** 关闭埋点:点 X、点遮罩、close 动作、被 replace 顶掉都会触发 */
118
- onClose?: DslAction;
119
- }
120
- interface DslView {
121
- type: DslViewType;
122
- stage: DslStage;
123
- nodes: DslNode[];
124
- }
125
- /** 后端数据源引用,名字必须在 sources 白名单内 */
126
- interface DslSourceRef {
127
- $source: string;
128
- params?: Record<string, unknown>;
129
- }
130
- interface DslDerived {
131
- /** data 里的数组字段名 */
132
- list: string;
133
- /** state 里的下标字段名 */
134
- indexBy: string;
135
- }
136
- /** 一份完整配置。单视图直接写 type/stage/nodes,多视图写 views + entry */
137
- interface Dsl extends Partial<DslView> {
138
- version: number;
139
- meta?: {
140
- name?: string;
141
- [key: string]: unknown;
142
- };
143
- data?: Record<string, unknown | DslSourceRef>;
144
- state?: Record<string, unknown>;
145
- derived?: Record<string, DslDerived>;
146
- views?: Record<string, DslView>;
147
- entry?: string;
148
- }
149
- /** 已经算好的行内样式,键是 CSS 属性名(驼峰),值是最终字符串 */
150
- type CssStyle = Record<string, string>;
151
- interface RenderElement {
152
- /** 稳定 key,框架层做列表 diff 用 */
153
- key: string;
154
- /** 画成什么。div 带 text 就是文本节点,带 children 就是容器 */
155
- tag: 'div' | 'img';
156
- /**
157
- * 类名,如 "dsl-node dsl-text is-clickable"。
158
- * 不参与样式(样式全在 style 里),纯粹是留给调试和宿主微调的钩子。
159
- * 由 core 统一生成,不让各框架壳各拼一套,否则 DOM 结构会慢慢跑偏。
160
- */
161
- className: string;
162
- style: CssStyle;
163
- text?: string;
164
- src?: string;
165
- /**
166
- * 有值才是可点的,动作和上下文都已经绑好,框架层直接挂上去即可。
167
- *
168
- * 【框架层必须遵守】挂上去时要阻止冒泡。
169
- * 父子都可点是常见排版——公告条整条点开弹窗、行末 × 关闭公告条——
170
- * 不阻止的话点 × 会连带触发外层,关掉的瞬间又弹出来。
171
- * 没有 onClick 的节点不要绑任何事件,让它正常冒泡到有 onClick 的祖先。
172
- */
173
- onClick?: () => void;
174
- children?: RenderElement[];
175
- }
176
- /**
177
- * 一个视图层。框架层照着这个结构套四层 div 就行,不需要自带任何 CSS:
178
- *
179
- * div(layerStyle)
180
- * div(maskStyle) ← mask 为 true 时才画
181
- * div(scrollStyle)
182
- * div(stageStyle)
183
- * div(clipStyle) ← 圆角裁切,关闭按钮留在外面才能溢出
184
- * ...nodes
185
- * closeButton ← 有配才有
186
- */
187
- interface RenderLayer {
188
- /** 视图名,同时用作 key */
189
- name: string;
190
- type: DslViewType;
191
- isTop: boolean;
192
- /** 画不画遮罩。只有栈顶的弹窗层才为 true */
193
- mask: boolean;
194
- onMaskClick?: () => void;
195
- layerStyle: CssStyle;
196
- maskStyle: CssStyle;
197
- scrollStyle: CssStyle;
198
- stageStyle: CssStyle;
199
- clipStyle: CssStyle;
200
- /** 关闭按钮,没配就是 undefined */
201
- closeButton?: RenderElement;
202
- nodes: RenderElement[];
203
- }
204
- interface RenderTree {
205
- /** 数据没回来或加载失败时为 false,框架层什么都不画 */
206
- ready: boolean;
207
- /** 有弹窗层时框架层需要撑满宿主容器 */
208
- hasPopup: boolean;
209
- /** 根容器样式,已按 hasPopup 算好 */
210
- rootStyle: CssStyle;
211
- layers: RenderLayer[];
212
- /** 这一轮里所有倒计时的结束时间戳,运行时据此决定要不要起定时器 */
213
- countdownEndTimes: number[];
214
- /** 只要有一个倒计时要厘秒,整体就按厘秒刷 */
215
- countdownPrecision: 's' | 'cs';
216
- }
217
- type DslSource = (params: Record<string, unknown>, user: Record<string, unknown>) => unknown | Promise<unknown>;
218
- type DslHandler = (params: Record<string, unknown>) => void;
219
- interface RuntimeEvents {
220
- ready: {
221
- keys: string[];
222
- };
223
- close: {
224
- reason: string;
225
- };
226
- track: {
227
- event: string;
228
- params: Record<string, unknown>;
229
- };
230
- navigate: {
231
- url: string;
232
- target: string;
233
- };
234
- call: {
235
- name: string;
236
- params: Record<string, unknown>;
237
- };
238
- 'state-change': Record<string, unknown>;
239
- 'view-change': {
240
- view: string;
241
- mode: string;
242
- stack: string[];
243
- closed?: string;
244
- };
245
- open: {
246
- view: string;
247
- mode: string;
248
- };
249
- error: {
250
- type: 'unknown-source';
251
- name: string;
252
- } | {
253
- type: 'unknown-view';
254
- name: string;
255
- } | {
256
- type: 'unknown-handler';
257
- name: string;
258
- } | {
259
- type: 'unknown-action';
260
- action: string;
261
- } | {
262
- type: 'unsafe-url';
263
- raw: string;
264
- } | {
265
- type: 'data-source-failed';
266
- message: string;
267
- };
268
- }
269
- type RuntimeEventName = keyof RuntimeEvents;
270
- type RuntimeEmit = <K extends RuntimeEventName>(event: K, payload: RuntimeEvents[K]) => void;
271
- interface RuntimeOptions {
272
- /** 宿主注入的用户上下文,配置里用 {{ user.xxx }} 读 */
273
- user?: Record<string, unknown>;
274
- /** 数据源白名单 */
275
- sources?: Record<string, DslSource>;
276
- /** call 动作的方法白名单 */
277
- handlers?: Record<string, DslHandler>;
278
- /** 编辑态:动作只上报不真正执行 */
279
- editMode?: boolean;
280
- /** 所有事件都从这里出去,宿主自己决定怎么处理 */
281
- emit?: RuntimeEmit;
282
- }
1
+ import { D as Dsl, R as RuntimeOptions, a as RenderTree, b as DslView, c as DslStyle, C as CssStyle } from './types-BgUaJUAu.cjs';
2
+ export { q as DslAction, h as DslActionBase, o as DslCallAction, j as DslCloseAction, k as DslCloseAllAction, t as DslCloseButton, s as DslClosePosition, w as DslDerived, A as DslHandler, f as DslLength, i as DslNavigateAction, r as DslNode, e as DslNodeType, l as DslOpenAction, g as DslRect, p as DslSequenceAction, m as DslSetStateAction, z as DslSource, v as DslSourceRef, u as DslStage, n as DslTrackAction, d as DslViewType, x as RenderElement, y as RenderLayer, F as RuntimeEmit, E as RuntimeEventName, B as RuntimeEvents } from './types-BgUaJUAu.cjs';
283
3
 
284
4
  /**
285
5
  * 运行时。
@@ -334,6 +54,13 @@ interface ValidateResult {
334
54
  declare const DSL_VERSION = 1;
335
55
  declare const NODE_TYPES: string[];
336
56
  declare const ACTION_TYPES: string[];
57
+ /**
58
+ * 埋点事件名格式:小写字母开头,后接小写字母 / 数字 / 下划线,总长 3~64。
59
+ *
60
+ * 只校验格式、不做白名单——事件名由运营自由定义,后端按前缀聚合,
61
+ * 格式统一才能聚合得干净(中文、驼峰、中划线混着来就没法分组)。
62
+ */
63
+ declare const TRACK_EVENT_PATTERN: RegExp;
337
64
  declare const CLOSE_POSITIONS: string[];
338
65
  declare function validate(dsl: Dsl | unknown): ValidateResult;
339
66
  /** 把校验结果拼成可以直接展示的文本 */
@@ -438,9 +165,16 @@ interface CountdownParts {
438
165
  type CountdownPrecision = 's' | 'cs';
439
166
  /** 把 to 解析成时间戳。兼容 '2026-09-01 00:00:00' 这种 Safari 不认的格式 */
440
167
  declare function parseEndTime(to: unknown): number;
168
+ /**
169
+ * 判断一个 to 能不能解析。给校验用。
170
+ *
171
+ * 解析失败会当成「已结束」,页面上只会看到一行「已结束」,
172
+ * 运营根本意识不到是自己时间写错了——所以必须在保存前就拦下来。
173
+ */
174
+ declare function isValidEndTime(to: unknown): boolean;
441
175
  /** 算出某个时刻的时间片段 */
442
176
  declare function computeParts(endTime: number, now?: number): CountdownParts;
443
177
  /** 不给 children 时退化成一行文本,占位符 {d} {h} {hAll} {m} {s} {cs} */
444
178
  declare function formatParts(parts: CountdownParts, format?: string): string;
445
179
 
446
- export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, type CssStyle, DSL_VERSION, type Dsl, type DslAction, type DslActionBase, type DslCallAction, type DslCloseAction, type DslCloseAllAction, type DslCloseButton, type DslClosePosition, type DslDerived, type DslHandler, type DslLength, type DslNavigateAction, type DslNode, type DslNodeType, type DslOpenAction, type DslRect, type DslRuntime, type DslSequenceAction, type DslSetStateAction, type DslSource, type DslSourceRef, type DslStage, type DslStyle, type DslTrackAction, type DslView, type DslViewType, type Issue, NODE_TYPES, type NormalizedViews, type RenderElement, type RenderLayer, type RenderTree, type RuntimeEmit, type RuntimeEventName, type RuntimeEvents, type RuntimeOptions, SINGLE_VIEW_NAME, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate };
180
+ export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, CssStyle, DSL_VERSION, Dsl, type DslRuntime, DslStyle, DslView, type Issue, NODE_TYPES, type NormalizedViews, RenderTree, RuntimeOptions, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate };