@yaoxiu/marketing-dsl 2.3.0 → 2.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.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-Bj0xyWnx.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, E as DslInteractionEvent, B as DslInteractionTrigger, 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, H as RuntimeEmit, G as RuntimeEventName, F as RuntimeEvents } from './types-Bj0xyWnx.cjs';
1
+ import { D as Dsl, R as RuntimeOptions, a as RenderTree, b as DslView, c as DslStyle, C as CssStyle } from './types-8FNxDXp0.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-8FNxDXp0.cjs';
3
3
 
4
4
  /**
5
5
  * 运行时。
@@ -271,6 +271,58 @@ declare function safeUrl(input: unknown): string;
271
271
  /** 图片额外允许 data:image/*,方便 base64 占位图 */
272
272
  declare function safeImageUrl(input: unknown): string;
273
273
 
274
+ /**
275
+ * CSS 文本的值处理。
276
+ *
277
+ * 单独成文件是为了让 `hover.ts` 能复用而不与 `css.ts` 形成循环引用
278
+ * (`css.ts` 要拿 `hover.ts` 的预收集结果,反过来 `hover.ts` 只需要这几个纯函数)。
279
+ *
280
+ * 【安全】这里是整个包里唯一做字符串拼接产出样式的地方。行内样式那条路径由框架
281
+ * (Vue 的 style 绑定)负责转义,值再怪也只是不生效;而 CSS 文本是裸拼的,一个 `}`
282
+ * 就能提前闭合规则、接着写任意选择器。所以每个值都要过 `sanitizeCssValue`,
283
+ * 不合格的整条声明直接丢弃 —— 宁可少一条样式,也不能让配置写进任意 CSS。
284
+ */
285
+
286
+ /**
287
+ * CSS 值安全过滤。
288
+ *
289
+ * 含 `{` `}` `<` `>` `;` `@` 或注释起始 `/*` 的值一律判为不安全,返回 `null`,
290
+ * 调用方据此丢弃**整条声明**(不是把字符删掉——半截值同样可能拼出意料外的效果)。
291
+ *
292
+ * @returns 安全的值字符串;不安全或空值返回 null
293
+ */
294
+ declare function sanitizeCssValue(value: unknown): string | null;
295
+
296
+ /**
297
+ * CSS 文本生成。
298
+ *
299
+ * 行内样式表达不了两件事:`@keyframes` 和 `:hover`。所以这一份配置除了渲染树之外,
300
+ * 还要产出一段 CSS 文本(`RenderTree.css`),由框架壳原样塞进一个 `<style>` 元素。
301
+ *
302
+ * 【安全】这是整个包里**唯一**做字符串拼接产出样式的地方。行内样式那条路径由框架
303
+ * (Vue 的 style 绑定)负责转义,值再怪也只是不生效;而 CSS 文本是裸拼的,一个 `}`
304
+ * 就能提前闭合规则、接着写任意选择器(改宿主页面的样式、拿 `content` 做钓鱼文案)。
305
+ * 所以这里的每个值都要过 `sanitizeCssValue`,每个名字都要过 `CSS_NAME_PATTERN`,
306
+ * 不合格的整条声明直接丢弃 —— 宁可少一条动画,也不能让配置写进任意 CSS。
307
+ *
308
+ * 【作用域】同一个页面上可能同时挂着好几份物料,keyframes 名和 hover 类名都是全局的,
309
+ * 重名就会互相覆盖。所以每个运行时实例领一个自增 uid,名字全部带上它。
310
+ */
311
+
312
+ /** keyframes 名与作用域类名允许的字符:字母开头,字母 / 数字 / 下划线 / 中划线,最长 32 */
313
+ declare const CSS_NAME_PATTERN: RegExp;
314
+ /**
315
+ * 缓动函数白名单。
316
+ *
317
+ * `cubic-bezier(...)` 单独放行(四个数字参数),其余一律按关键字精确匹配 ——
318
+ * 不做「看着像就放行」的模糊判断,缓动值同样会被拼进 CSS 文本。
319
+ */
320
+ declare const ALLOWED_EASINGS: string[];
321
+ /** 校验用:缓动值是否合法 */
322
+ declare function isValidEasing(value: unknown): boolean;
323
+ /** 校验用:关键帧的偏移键是否合法 */
324
+ declare function isKeyframeOffset(value: unknown): boolean;
325
+
274
326
  /**
275
327
  * 倒计时。只负责「算时间」,不负责「长什么样」。
276
328
  *
@@ -312,4 +364,4 @@ declare function computeParts(endTime: number, now?: number): CountdownParts;
312
364
  /** 不给 children 时退化成一行文本,占位符 {d} {h} {hAll} {m} {s} {cs} */
313
365
  declare function formatParts(parts: CountdownParts, format?: string): string;
314
366
 
315
- 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, 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, isLength, isRegisteredHostHandler, isRegisteredHostSource, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate, validateUserFields };
367
+ 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 };
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-Bj0xyWnx.js';
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, E as DslInteractionEvent, B as DslInteractionTrigger, 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, H as RuntimeEmit, G as RuntimeEventName, F as RuntimeEvents } from './types-Bj0xyWnx.js';
1
+ import { D as Dsl, R as RuntimeOptions, a as RenderTree, b as DslView, c as DslStyle, C as CssStyle } from './types-8FNxDXp0.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-8FNxDXp0.js';
3
3
 
4
4
  /**
5
5
  * 运行时。
@@ -271,6 +271,58 @@ declare function safeUrl(input: unknown): string;
271
271
  /** 图片额外允许 data:image/*,方便 base64 占位图 */
272
272
  declare function safeImageUrl(input: unknown): string;
273
273
 
274
+ /**
275
+ * CSS 文本的值处理。
276
+ *
277
+ * 单独成文件是为了让 `hover.ts` 能复用而不与 `css.ts` 形成循环引用
278
+ * (`css.ts` 要拿 `hover.ts` 的预收集结果,反过来 `hover.ts` 只需要这几个纯函数)。
279
+ *
280
+ * 【安全】这里是整个包里唯一做字符串拼接产出样式的地方。行内样式那条路径由框架
281
+ * (Vue 的 style 绑定)负责转义,值再怪也只是不生效;而 CSS 文本是裸拼的,一个 `}`
282
+ * 就能提前闭合规则、接着写任意选择器。所以每个值都要过 `sanitizeCssValue`,
283
+ * 不合格的整条声明直接丢弃 —— 宁可少一条样式,也不能让配置写进任意 CSS。
284
+ */
285
+
286
+ /**
287
+ * CSS 值安全过滤。
288
+ *
289
+ * 含 `{` `}` `<` `>` `;` `@` 或注释起始 `/*` 的值一律判为不安全,返回 `null`,
290
+ * 调用方据此丢弃**整条声明**(不是把字符删掉——半截值同样可能拼出意料外的效果)。
291
+ *
292
+ * @returns 安全的值字符串;不安全或空值返回 null
293
+ */
294
+ declare function sanitizeCssValue(value: unknown): string | null;
295
+
296
+ /**
297
+ * CSS 文本生成。
298
+ *
299
+ * 行内样式表达不了两件事:`@keyframes` 和 `:hover`。所以这一份配置除了渲染树之外,
300
+ * 还要产出一段 CSS 文本(`RenderTree.css`),由框架壳原样塞进一个 `<style>` 元素。
301
+ *
302
+ * 【安全】这是整个包里**唯一**做字符串拼接产出样式的地方。行内样式那条路径由框架
303
+ * (Vue 的 style 绑定)负责转义,值再怪也只是不生效;而 CSS 文本是裸拼的,一个 `}`
304
+ * 就能提前闭合规则、接着写任意选择器(改宿主页面的样式、拿 `content` 做钓鱼文案)。
305
+ * 所以这里的每个值都要过 `sanitizeCssValue`,每个名字都要过 `CSS_NAME_PATTERN`,
306
+ * 不合格的整条声明直接丢弃 —— 宁可少一条动画,也不能让配置写进任意 CSS。
307
+ *
308
+ * 【作用域】同一个页面上可能同时挂着好几份物料,keyframes 名和 hover 类名都是全局的,
309
+ * 重名就会互相覆盖。所以每个运行时实例领一个自增 uid,名字全部带上它。
310
+ */
311
+
312
+ /** keyframes 名与作用域类名允许的字符:字母开头,字母 / 数字 / 下划线 / 中划线,最长 32 */
313
+ declare const CSS_NAME_PATTERN: RegExp;
314
+ /**
315
+ * 缓动函数白名单。
316
+ *
317
+ * `cubic-bezier(...)` 单独放行(四个数字参数),其余一律按关键字精确匹配 ——
318
+ * 不做「看着像就放行」的模糊判断,缓动值同样会被拼进 CSS 文本。
319
+ */
320
+ declare const ALLOWED_EASINGS: string[];
321
+ /** 校验用:缓动值是否合法 */
322
+ declare function isValidEasing(value: unknown): boolean;
323
+ /** 校验用:关键帧的偏移键是否合法 */
324
+ declare function isKeyframeOffset(value: unknown): boolean;
325
+
274
326
  /**
275
327
  * 倒计时。只负责「算时间」,不负责「长什么样」。
276
328
  *
@@ -312,4 +364,4 @@ declare function computeParts(endTime: number, now?: number): CountdownParts;
312
364
  /** 不给 children 时退化成一行文本,占位符 {d} {h} {hAll} {m} {s} {cs} */
313
365
  declare function formatParts(parts: CountdownParts, format?: string): string;
314
366
 
315
- 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, 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, isLength, isRegisteredHostHandler, isRegisteredHostSource, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate, validateUserFields };
367
+ 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 };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { normalizeViews, createTicker, evaluate, resolveNodeStyle, interpolateDeep, interpolate, toLength, toCssStyle, parseEndTime, computeParts, formatParts } from './chunk-DTHKC5AL.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-DTHKC5AL.js';
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';
3
3
 
4
4
  // src/url.ts
5
5
  var SAFE_PROTOCOLS = ["http:", "https:", "mailto:", "tel:"];
@@ -103,17 +103,13 @@ function createDispatcher(options) {
103
103
  // src/resolve.ts
104
104
  function resolveTree(input) {
105
105
  const { normalized, viewStack, ready } = input;
106
+ const css = createCssBuilder(input.scopeUid, input.keyframes, input.hover);
107
+ const pass = Object.assign({}, input, { css });
106
108
  const layers = viewStack.map((name) => ({ name, view: normalized.views[name] })).filter((item) => !!item.view);
107
109
  const hasPopup = layers.some((item) => item.view.type === "popup");
108
110
  const countdowns = { endTimes: [], precision: "s" };
109
111
  const renderLayers = ready ? layers.map(
110
- (item, index) => resolveLayer(
111
- item.name,
112
- item.view,
113
- index === layers.length - 1,
114
- input,
115
- countdowns
116
- )
112
+ (item, index) => resolveLayer(item.name, item.view, index === layers.length - 1, pass, countdowns)
117
113
  ) : [];
118
114
  return {
119
115
  ready: ready && layers.length > 0,
@@ -124,7 +120,10 @@ function resolveTree(input) {
124
120
  rootStyle: { position: "relative" },
125
121
  layers: renderLayers,
126
122
  countdownEndTimes: countdowns.endTimes,
127
- countdownPrecision: countdowns.precision
123
+ countdownPrecision: countdowns.precision,
124
+ // build 必须在 layers 算完之后调:hover 规则是遍历节点时才登记进来的
125
+ css: css.build(),
126
+ rootClassName: css.rootClassName
128
127
  };
129
128
  }
130
129
  function resolveLayer(name, view, isTop, input, countdowns) {
@@ -133,7 +132,7 @@ function resolveLayer(name, view, isTop, input, countdowns) {
133
132
  const isPopup = view.type === "popup";
134
133
  const rootLayout = stage.layout === "flow" ? "flow" : "absolute";
135
134
  const maskClosable = !!stage.maskClosable;
136
- const onMaskClick = maskClosable ? () => closeTop("mask") : void 0;
135
+ const onMaskClick = maskClosable ? stage.maskAction ? () => input.dispatch(stage.maskAction, context) : () => closeTop("mask") : void 0;
137
136
  const nodes = (view.nodes || []).map(
138
137
  (node, index) => resolveNode(node, `${name}-${index}`, rootLayout, context, input, countdowns)
139
138
  ).filter((el) => !!el);
@@ -141,20 +140,26 @@ function resolveLayer(name, view, isTop, input, countdowns) {
141
140
  name,
142
141
  type: view.type,
143
142
  isTop,
144
- // 只有栈顶那层画遮罩,否则两层遮罩叠加会明显变黑
145
- mask: isPopup && stage.mask !== false && isTop,
146
- onMaskClick,
143
+ /**
144
+ * 弹窗层恒画遮罩元素,**非栈顶时只是变透明**(见下方 maskStyle)。
145
+ *
146
+ * 【为什么不是「只有栈顶才画」】那样写视觉上也对(两层实心遮罩叠加会明显变黑,
147
+ * 所以下层必须让位),但下层的遮罩 DOM 会被整个移除、退回栈顶时再新建一个 ——
148
+ * 新元素身上的 `maskAnimate` 于是又播一遍:关掉二次挽留层回到主弹窗时,
149
+ * 背景会重新淡入一次,看起来就是「闪了一下」。元素常驻就没这问题。
150
+ */
151
+ mask: isPopup && stage.mask !== false,
152
+ // 非栈顶层整层 pointerEvents: none,遮罩点不到,这里也就不必给它挂回调
153
+ onMaskClick: isTop ? onMaskClick : void 0,
147
154
  layerStyle: layerStyle(isPopup, isTop),
148
- maskStyle: {
149
- position: "absolute",
150
- top: "0",
151
- right: "0",
152
- bottom: "0",
153
- left: "0",
154
- background: "rgba(0, 0, 0, 0.55)"
155
- },
155
+ // 合并顺序固定:内置默认 → stage.maskStyle 覆盖 → maskAnimate 的 animation 收尾。
156
+ // 遮罩样式也走一遍插值,切 tab 时能跟着变(同 stage.style)
157
+ maskStyle: maskStyle(stage, isTop, context, input),
156
158
  scrollStyle: scrollStyle(isPopup),
157
- stageStyle: resolveStageStyle(stage, isPopup, context),
159
+ stageStyle: withAnimation(
160
+ resolveStageStyle(stage, isPopup, context),
161
+ input.css.animation(stage.animate)
162
+ ),
158
163
  clipStyle: {
159
164
  position: "relative",
160
165
  height: "100%",
@@ -162,10 +167,39 @@ function resolveLayer(name, view, isTop, input, countdowns) {
162
167
  overflow: "hidden",
163
168
  boxSizing: "border-box"
164
169
  },
165
- closeButton: resolveCloseButton(stage.closeButton, name, closeTop),
170
+ closeButton: resolveCloseButton(stage.closeButton, name, closeTop, input, context),
166
171
  nodes
167
172
  };
168
173
  }
174
+ function withAnimation(style, animation) {
175
+ if (animation) style.animation = animation;
176
+ return style;
177
+ }
178
+ function maskStyle(stage, isTop, context, input) {
179
+ const style = withAnimation(
180
+ Object.assign(
181
+ {
182
+ position: "absolute",
183
+ top: "0",
184
+ right: "0",
185
+ bottom: "0",
186
+ left: "0",
187
+ background: "rgba(0, 0, 0, 0.55)"
188
+ },
189
+ toCssStyle(interpolateDeep(stage.maskStyle, context))
190
+ ),
191
+ input.css.animation(stage.maskAnimate)
192
+ );
193
+ if (isTop) return style;
194
+ style.background = "transparent";
195
+ delete style.backdropFilter;
196
+ delete style.WebkitBackdropFilter;
197
+ return style;
198
+ }
199
+ function withHover(className, owner, css) {
200
+ const hoverClass = css.hoverClass(owner);
201
+ return hoverClass ? `${className} ${hoverClass}` : className;
202
+ }
169
203
  function layerStyle(isPopup, isTop) {
170
204
  const style = isPopup ? {
171
205
  position: "fixed",
@@ -223,7 +257,8 @@ function resolveStageStyle(stage, isPopup, context) {
223
257
  }
224
258
  return style;
225
259
  }
226
- function resolveCloseButton(config, layerName, closeTop) {
260
+ function resolveCloseButton(config, layerName, closeTop, input, context) {
261
+ const css = input.css;
227
262
  if (!config || config.show === false) return void 0;
228
263
  const offset = config.offset || [8, 8];
229
264
  const size = config.size === void 0 ? 26 : config.size;
@@ -261,12 +296,14 @@ function resolveCloseButton(config, layerName, closeTop) {
261
296
  style.right = toLength(offset[0]);
262
297
  }
263
298
  Object.assign(style, toCssStyle(config.style));
264
- const onClick = () => closeTop("close-button");
299
+ withAnimation(style, css.animation(config.animate));
300
+ const className = withHover("dsl-close", config, css);
301
+ const onClick = config.action ? () => input.dispatch(config.action, context) : () => closeTop("close-button");
265
302
  if (iconMode === "image") {
266
303
  return {
267
304
  key: `${layerName}-close`,
268
305
  tag: "div",
269
- className: "dsl-close",
306
+ className,
270
307
  style,
271
308
  onClick,
272
309
  children: [
@@ -289,7 +326,7 @@ function resolveCloseButton(config, layerName, closeTop) {
289
326
  return {
290
327
  key: `${layerName}-close`,
291
328
  tag: "div",
292
- className: "dsl-close",
329
+ className,
293
330
  style,
294
331
  onClick,
295
332
  text: config.icon
@@ -298,7 +335,7 @@ function resolveCloseButton(config, layerName, closeTop) {
298
335
  return {
299
336
  key: `${layerName}-close`,
300
337
  tag: "div",
301
- className: "dsl-close",
338
+ className,
302
339
  style,
303
340
  onClick,
304
341
  children: [closeIcon(`${layerName}-close-icon`, size)]
@@ -386,6 +423,12 @@ function resolveNode(node, key, layout, context, input, countdowns) {
386
423
  layout
387
424
  )
388
425
  );
426
+ withAnimation(style, input.css.animation(node.animate));
427
+ const hoverClass = input.css.hoverClass(node);
428
+ const cls = (type, clickable) => {
429
+ const base = nodeClass(type, clickable);
430
+ return hoverClass ? `${base} ${hoverClass}` : base;
431
+ };
389
432
  const onClick = node.action ? () => input.dispatch(node.action, context) : void 0;
390
433
  if (onClick && !style.cursor) style.cursor = "pointer";
391
434
  switch (node.type) {
@@ -393,7 +436,7 @@ function resolveNode(node, key, layout, context, input, countdowns) {
393
436
  return {
394
437
  key,
395
438
  tag: "div",
396
- className: nodeClass("box", !!onClick),
439
+ className: cls("box", !!onClick),
397
440
  style: Object.assign({ position: "relative" }, style),
398
441
  onClick,
399
442
  children: resolveChildren(
@@ -409,7 +452,7 @@ function resolveNode(node, key, layout, context, input, countdowns) {
409
452
  return {
410
453
  key,
411
454
  tag: "div",
412
- className: nodeClass("flex", !!onClick),
455
+ className: cls("flex", !!onClick),
413
456
  style: Object.assign({ display: "flex" }, style),
414
457
  onClick,
415
458
  children: resolveChildren(node.children, key, "flow", context, input, countdowns)
@@ -418,7 +461,7 @@ function resolveNode(node, key, layout, context, input, countdowns) {
418
461
  return {
419
462
  key,
420
463
  tag: "div",
421
- className: nodeClass("repeat", !!onClick),
464
+ className: cls("repeat", !!onClick),
422
465
  style: Object.assign({ display: "flex" }, style),
423
466
  onClick,
424
467
  children: resolveRepeat(node, key, context, input, countdowns)
@@ -427,7 +470,7 @@ function resolveNode(node, key, layout, context, input, countdowns) {
427
470
  return {
428
471
  key,
429
472
  tag: "div",
430
- className: nodeClass("tabs", false),
473
+ className: cls("tabs", false),
431
474
  style: Object.assign({ display: "flex" }, style),
432
475
  children: resolveTabs(node, key, context, input)
433
476
  };
@@ -435,7 +478,7 @@ function resolveNode(node, key, layout, context, input, countdowns) {
435
478
  return {
436
479
  key,
437
480
  tag: "img",
438
- className: nodeClass("image", !!onClick),
481
+ className: cls("image", !!onClick),
439
482
  src: safeImageUrl(interpolate(node.src, context)),
440
483
  style: Object.assign({ display: "block", objectFit: "cover" }, style),
441
484
  onClick
@@ -444,7 +487,7 @@ function resolveNode(node, key, layout, context, input, countdowns) {
444
487
  return {
445
488
  key,
446
489
  tag: "div",
447
- className: nodeClass("text", !!onClick),
490
+ className: cls("text", !!onClick),
448
491
  style: Object.assign({ wordBreak: "break-word" }, style),
449
492
  text: toText(interpolate(node.content, context)),
450
493
  onClick
@@ -453,7 +496,7 @@ function resolveNode(node, key, layout, context, input, countdowns) {
453
496
  return {
454
497
  key,
455
498
  tag: "div",
456
- className: nodeClass("button", true),
499
+ className: cls("button", true),
457
500
  style: Object.assign(
458
501
  {
459
502
  display: "flex",
@@ -469,7 +512,7 @@ function resolveNode(node, key, layout, context, input, countdowns) {
469
512
  onClick
470
513
  };
471
514
  case "countdown":
472
- return resolveCountdown(node, key, style, context, input, countdowns);
515
+ return resolveCountdown(node, key, style, context, input, countdowns, hoverClass);
473
516
  // 配置比解释器新时,未知类型降级为不渲染,不阻断整个弹窗
474
517
  default:
475
518
  return void 0;
@@ -531,10 +574,23 @@ function resolveTabs(node, key, context, input) {
531
574
  };
532
575
  });
533
576
  }
534
- function resolveCountdown(node, key, style, context, input, countdowns) {
535
- var _a;
577
+ function resolveCountdownEndTime(node, key, context, input) {
536
578
  const endTime = parseEndTime(interpolate(node.to, context));
579
+ if (endTime > 0) return endTime;
580
+ const duration = Number(interpolate(node.duration, context));
581
+ if (!isFinite(duration) || duration <= 0) return 0;
582
+ let start = input.countdownStarts.get(key);
583
+ if (start === void 0) {
584
+ start = Date.now();
585
+ input.countdownStarts.set(key, start);
586
+ }
587
+ return start + duration;
588
+ }
589
+ function resolveCountdown(node, key, style, context, input, countdowns, hoverClass) {
590
+ var _a;
591
+ const endTime = resolveCountdownEndTime(node, key, context, input);
537
592
  const parts = computeParts(endTime);
593
+ const cls = (base) => hoverClass ? `${base} ${hoverClass}` : base;
538
594
  countdowns.endTimes.push(endTime);
539
595
  if (node.precision === "cs") countdowns.precision = "cs";
540
596
  if (endTime && node.onEnd) {
@@ -551,7 +607,7 @@ function resolveCountdown(node, key, style, context, input, countdowns) {
551
607
  return {
552
608
  key,
553
609
  tag: "div",
554
- className: "dsl-node dsl-text dsl-countdown",
610
+ className: cls("dsl-node dsl-text dsl-countdown"),
555
611
  style: Object.assign({ wordBreak: "break-word" }, style),
556
612
  text: node.endText || "\u5DF2\u7ED3\u675F"
557
613
  };
@@ -560,7 +616,7 @@ function resolveCountdown(node, key, style, context, input, countdowns) {
560
616
  return {
561
617
  key,
562
618
  tag: "div",
563
- className: "dsl-node dsl-text dsl-countdown",
619
+ className: cls("dsl-node dsl-text dsl-countdown"),
564
620
  style: Object.assign({ wordBreak: "break-word" }, style),
565
621
  text: formatParts(parts, node.format)
566
622
  };
@@ -569,7 +625,7 @@ function resolveCountdown(node, key, style, context, input, countdowns) {
569
625
  return {
570
626
  key,
571
627
  tag: "div",
572
- className: "dsl-node dsl-flex dsl-countdown",
628
+ className: cls("dsl-node dsl-flex dsl-countdown"),
573
629
  style: Object.assign({ display: "flex" }, style),
574
630
  children: resolveChildren(
575
631
  node.children,
@@ -585,6 +641,37 @@ function toText(value) {
585
641
  return value === void 0 || value === null ? "" : String(value);
586
642
  }
587
643
 
644
+ // src/hover.ts
645
+ function collectHoverStyles(views, uid, context) {
646
+ const rules = [];
647
+ const classOf = /* @__PURE__ */ new WeakMap();
648
+ const scope = `.dsl-r${uid}`;
649
+ let seed = 0;
650
+ const register = (owner, style) => {
651
+ if (!owner || !style || typeof style !== "object") return;
652
+ const body = declarations(toCssStyle(interpolateDeep(style, context)));
653
+ if (!body) return;
654
+ seed += 1;
655
+ const className = `dsl-hv-${uid}-${seed}`;
656
+ classOf.set(owner, className);
657
+ rules.push(`${scope} .${className}:hover{${body}}`);
658
+ };
659
+ const walkNode = (node) => {
660
+ if (!node || typeof node !== "object") return;
661
+ register(node, node.hoverStyle);
662
+ (node.children || []).forEach(walkNode);
663
+ walkNode(node.template);
664
+ };
665
+ Object.keys(views).forEach((name) => {
666
+ const view = views[name];
667
+ if (!view || typeof view !== "object") return;
668
+ const closeButton = view.stage && view.stage.closeButton;
669
+ register(closeButton, closeButton && closeButton.hoverStyle);
670
+ (view.nodes || []).forEach(walkNode);
671
+ });
672
+ return { rules, classOf };
673
+ }
674
+
588
675
  // src/runtime.ts
589
676
  var noopEmit = () => {
590
677
  };
@@ -596,6 +683,7 @@ function createRuntime(dsl, options = {}) {
596
683
  editMode: !!options.editMode
597
684
  };
598
685
  const normalized = normalizeViews(dsl);
686
+ const scopeUid = nextScopeUid();
599
687
  let state = Object.assign({}, dsl.state || {});
600
688
  let resolvedData = {};
601
689
  let viewStack = [normalized.entry];
@@ -604,6 +692,7 @@ function createRuntime(dsl, options = {}) {
604
692
  let destroyed = false;
605
693
  const listeners = /* @__PURE__ */ new Set();
606
694
  const countdownEnds = /* @__PURE__ */ new Map();
695
+ const countdownStarts = /* @__PURE__ */ new Map();
607
696
  const notify = () => {
608
697
  if (destroyed) return;
609
698
  listeners.forEach((fn) => fn());
@@ -737,7 +826,9 @@ function createRuntime(dsl, options = {}) {
737
826
  });
738
827
  }
739
828
  loadData();
829
+ let hover;
740
830
  function getTree() {
831
+ if (!hover) hover = collectHoverStyles(normalized.views, scopeUid, buildContext());
741
832
  const tree = resolveTree({
742
833
  normalized,
743
834
  viewStack,
@@ -747,7 +838,11 @@ function createRuntime(dsl, options = {}) {
747
838
  dispatch,
748
839
  setState,
749
840
  closeTop,
750
- countdownEnds
841
+ countdownEnds,
842
+ countdownStarts,
843
+ scopeUid,
844
+ keyframes: dsl.keyframes,
845
+ hover
751
846
  });
752
847
  ticker.sync(tree.countdownEndTimes, tree.countdownPrecision);
753
848
  return tree;
@@ -1,4 +1,4 @@
1
- import { G as RuntimeEventName, E as DslInteractionEvent } from '../types-Bj0xyWnx.cjs';
1
+ import { I as RuntimeEventName, G as DslInteractionEvent } from '../types-8FNxDXp0.cjs';
2
2
 
3
3
  /**
4
4
  * 营销物料上报载荷类型(上报契约的唯一数据源)。
@@ -1,4 +1,4 @@
1
- import { G as RuntimeEventName, E as DslInteractionEvent } from '../types-Bj0xyWnx.js';
1
+ import { I as RuntimeEventName, G as DslInteractionEvent } from '../types-8FNxDXp0.js';
2
2
 
3
3
  /**
4
4
  * 营销物料上报载荷类型(上报契约的唯一数据源)。