@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.
@@ -15,6 +15,40 @@ type DslLength = number | string;
15
15
  type DslRect = [DslLength, DslLength, DslLength, DslLength];
16
16
  /** 样式对象,键必须在白名单内,值可以带 {{ }} */
17
17
  type DslStyle = Record<string, unknown>;
18
+ /**
19
+ * 动画声明。
20
+ *
21
+ * 只描述「播哪一组 keyframes、怎么播」,关键帧本身写在 `Dsl.keyframes` 里。
22
+ * 解释器把它落成**行内** `animation` 简写,keyframes 定义才进 `RenderTree.css`——
23
+ * 这样同一份配置在页面上出现多次也不会互相污染(keyframes 名带作用域前缀)。
24
+ */
25
+ interface DslAnimate {
26
+ /** keyframes 名:必须在 `dsl.keyframes` 里定义过 */
27
+ name: string;
28
+ /** 时长,毫秒 */
29
+ duration: number;
30
+ /** 延迟,毫秒,默认 0。交错入场就靠它错开 */
31
+ delay?: number;
32
+ /**
33
+ * 缓动函数。白名单:
34
+ * linear / ease / ease-in / ease-out / ease-in-out / step-start / step-end /
35
+ * cubic-bezier(x, x, x, x)。默认 `ease`
36
+ */
37
+ easing?: string;
38
+ /** 播放次数,默认 1;`'infinite'` 表示无限循环(呼吸、扫光这类) */
39
+ iteration?: number | 'infinite';
40
+ /** 播放方向,默认 `normal` */
41
+ direction?: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';
42
+ /** 填充模式,默认 `both`(入场动画必须保留终态,否则播完会弹回初始值) */
43
+ fill?: 'none' | 'forwards' | 'backwards' | 'both';
44
+ }
45
+ /**
46
+ * 一组关键帧:偏移量 → 样式。
47
+ *
48
+ * 偏移量只接受 `'from'` / `'to'` / `'0%'` 这三种写法;
49
+ * 样式仍要过 style 白名单,写了白名单外的属性会被丢弃。
50
+ */
51
+ type DslKeyframe = Record<string, DslStyle>;
18
52
  interface DslActionBase {
19
53
  type: string;
20
54
  }
@@ -65,6 +99,15 @@ interface DslNode {
65
99
  /** 表达式,为假时整个节点不渲染 */
66
100
  visibleWhen?: string;
67
101
  action?: DslAction;
102
+ /** 入场 / 循环动画 */
103
+ animate?: DslAnimate;
104
+ /**
105
+ * 鼠标悬停时叠加的样式。
106
+ *
107
+ * **只会生成 CSS 规则,不进行内联**——行内样式没有 `:hover` 这个东西,
108
+ * 想在行内做只能靠 JS 监听鼠标事件,那既要框架壳配合、又天然丢触屏。
109
+ */
110
+ hoverStyle?: DslStyle;
68
111
  /** box / flex / countdown */
69
112
  children?: DslNode[];
70
113
  /** repeat / tabs:数组来源表达式 */
@@ -86,6 +129,14 @@ interface DslNode {
86
129
  text?: string;
87
130
  /** countdown */
88
131
  to?: string;
132
+ /**
133
+ * countdown 的相对时长(毫秒)。
134
+ *
135
+ * 从**该视图展示那一刻**起算,「每次打开重新倒 5 分钟」这类写法用它。
136
+ * 与 `to` 同时存在时 `to` 优先(绝对时间更明确)。
137
+ * 起算时间戳钉在运行时里,重算渲染树不会把剩余时间刷回去。
138
+ */
139
+ duration?: number;
89
140
  precision?: 's' | 'cs';
90
141
  as?: string;
91
142
  format?: string;
@@ -95,6 +146,15 @@ interface DslNode {
95
146
  type DslClosePosition = 'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left' | 'bottom-center';
96
147
  interface DslCloseButton {
97
148
  show?: boolean;
149
+ /**
150
+ * 点它做什么。
151
+ *
152
+ * 不配就是默认行为「关掉当前这一层」(多视图里等于弹栈退回上一层,单视图即整体关闭)。
153
+ * 配了就**只**执行这个动作、不再自动关层 —— 「点 ✕ 先弹二次挽留」正是靠它:
154
+ * `{ "type": "open", "view": "confirm", "mode": "stack" }`。
155
+ * 想在动作里顺带关掉,自己写 `close` / `closeAll` 进去。
156
+ */
157
+ action?: DslAction;
98
158
  position?: DslClosePosition;
99
159
  /** [x, y],负值把按钮移到弹窗外面 */
100
160
  offset?: [DslLength, DslLength];
@@ -102,6 +162,10 @@ interface DslCloseButton {
102
162
  icon?: string;
103
163
  image?: string;
104
164
  style?: DslStyle;
165
+ /** 悬停样式,只生成 CSS 规则 */
166
+ hoverStyle?: DslStyle;
167
+ /** 关闭按钮自己的动画(如延迟淡入,先让用户看内容) */
168
+ animate?: DslAnimate;
105
169
  }
106
170
  interface DslStage {
107
171
  width?: DslLength;
@@ -110,8 +174,30 @@ interface DslStage {
110
174
  layout?: 'absolute' | 'flow';
111
175
  mask?: boolean;
112
176
  maskClosable?: boolean;
177
+ /**
178
+ * 点遮罩做什么。
179
+ *
180
+ * 不配就是默认行为「关掉当前这一层」。配了就执行这个动作而不关层
181
+ * (原页面点遮罩弹二次挽留就是这么来的)。
182
+ *
183
+ * 与 `maskClosable` 各管一件事、不冲突:`maskClosable: false` 时遮罩根本不响应点击,
184
+ * 这个动作也不会执行;`maskAction` 只决定「点了之后做什么」。
185
+ */
186
+ maskAction?: DslAction;
113
187
  closeButton?: DslCloseButton;
188
+ /**
189
+ * 遮罩自身样式,走 style 白名单。
190
+ *
191
+ * 内置默认是 `rgba(0, 0, 0, 0.55)` 纯色,这里配的会**覆盖**默认值
192
+ * (常用 `background` 改深浅、`backdropFilter: 'blur(4px)'` 加毛玻璃)。
193
+ * `maskAnimate` 生成的 `animation` 仍排在最后,不会被这里的配置盖掉。
194
+ */
195
+ maskStyle?: DslStyle;
114
196
  style?: DslStyle;
197
+ /** 舞台入场动画(弹窗主体的弹入 / 放大) */
198
+ animate?: DslAnimate;
199
+ /** 遮罩入场动画(一般是淡入,和舞台分开配才能各走各的时长) */
200
+ maskAnimate?: DslAnimate;
115
201
  /** 曝光埋点:数据就绪、视图真正展示时触发一次 */
116
202
  onShow?: DslAction;
117
203
  /** 关闭埋点:点 X、点遮罩、close 动作、被 replace 顶掉都会触发 */
@@ -145,6 +231,13 @@ interface Dsl extends Partial<DslView> {
145
231
  derived?: Record<string, DslDerived>;
146
232
  views?: Record<string, DslView>;
147
233
  entry?: string;
234
+ /**
235
+ * 关键帧库,`{ 名字: { 偏移量: 样式 } }`。
236
+ *
237
+ * 全配置共用一份(多视图之间也共用),`animate.name` 引用这里的名字。
238
+ * 名字只能是 `^[a-zA-Z][a-zA-Z0-9_-]{0,31}$`——它会被拼进 CSS 文本,必须可控。
239
+ */
240
+ keyframes?: Record<string, DslKeyframe>;
148
241
  }
149
242
  /** 已经算好的行内样式,键是 CSS 属性名(驼峰),值是最终字符串 */
150
243
  type CssStyle = Record<string, string>;
@@ -198,8 +291,15 @@ interface RenderLayer {
198
291
  name: string;
199
292
  type: DslViewType;
200
293
  isTop: boolean;
201
- /** 画不画遮罩。只有栈顶的弹窗层才为 true */
294
+ /**
295
+ * 画不画遮罩元素。弹窗层恒为 true(`stage.mask: false` 除外),**包括非栈顶的层**。
296
+ *
297
+ * 遮挡效果由 `maskStyle` 控制:非栈顶层的底色是 transparent,实际遮挡永远只有栈顶那层
298
+ * (两层实心遮罩叠加会明显变黑)。元素本身常驻是刻意的 —— 按 isTop 增删这个 DOM 节点,
299
+ * 会让下层的 `maskAnimate` 在退回栈顶时重播一遍,表现为「关掉二次挽留层,背景闪了一下」。
300
+ */
202
301
  mask: boolean;
302
+ /** 点遮罩的回调。只有栈顶那层才有:非栈顶层整层 pointerEvents: none,点不到 */
203
303
  onMaskClick?: () => void;
204
304
  layerStyle: CssStyle;
205
305
  maskStyle: CssStyle;
@@ -227,6 +327,20 @@ interface RenderTree {
227
327
  countdownEndTimes: number[];
228
328
  /** 只要有一个倒计时要厘秒,整体就按厘秒刷 */
229
329
  countdownPrecision: 's' | 'cs';
330
+ /**
331
+ * 这份配置要注入页面的全部 CSS 文本(keyframes 定义 + hover 规则 + reduced-motion 兜底)。
332
+ *
333
+ * 没有动画也没有 hover 时是**空串**,此时框架壳不该建 `<style>` 元素。
334
+ * 内容已经过安全过滤,壳直接塞进 `<style>` 即可,**不得在壳里做任何生成或判断**。
335
+ */
336
+ css: string;
337
+ /**
338
+ * 根容器类名,形如 `dsl-renderer dsl-r3`。
339
+ *
340
+ * 后半截是这个运行时实例的作用域前缀,`css` 里的每条规则都挂在它下面——
341
+ * 同一个页面上出现多份物料时,各自的 hover 规则不会串台。
342
+ */
343
+ rootClassName: string;
230
344
  }
231
345
  type DslSource = (params: Record<string, unknown>, user: Record<string, unknown>) => unknown | Promise<unknown>;
232
346
  type DslHandler = (params: Record<string, unknown>) => void;
@@ -342,4 +456,4 @@ interface RuntimeOptions {
342
456
  emit?: RuntimeEmit;
343
457
  }
344
458
 
345
- export type { DslHandler as A, DslInteractionTrigger as B, CssStyle as C, Dsl as D, DslInteractionEvent as E, RuntimeEvents as F, RuntimeEventName as G, RuntimeEmit as H, RuntimeOptions as R, RenderTree as a, DslView as b, DslStyle as c, DslViewType as d, DslNodeType as e, DslLength as f, DslRect as g, DslActionBase as h, DslNavigateAction as i, DslCloseAction as j, DslCloseAllAction as k, DslOpenAction as l, DslSetStateAction as m, DslTrackAction as n, DslCallAction as o, DslSequenceAction as p, DslAction as q, DslNode as r, DslClosePosition as s, DslCloseButton as t, DslStage as u, DslSourceRef as v, DslDerived as w, RenderElement as x, RenderLayer as y, DslSource as z };
459
+ 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 };
@@ -15,6 +15,40 @@ type DslLength = number | string;
15
15
  type DslRect = [DslLength, DslLength, DslLength, DslLength];
16
16
  /** 样式对象,键必须在白名单内,值可以带 {{ }} */
17
17
  type DslStyle = Record<string, unknown>;
18
+ /**
19
+ * 动画声明。
20
+ *
21
+ * 只描述「播哪一组 keyframes、怎么播」,关键帧本身写在 `Dsl.keyframes` 里。
22
+ * 解释器把它落成**行内** `animation` 简写,keyframes 定义才进 `RenderTree.css`——
23
+ * 这样同一份配置在页面上出现多次也不会互相污染(keyframes 名带作用域前缀)。
24
+ */
25
+ interface DslAnimate {
26
+ /** keyframes 名:必须在 `dsl.keyframes` 里定义过 */
27
+ name: string;
28
+ /** 时长,毫秒 */
29
+ duration: number;
30
+ /** 延迟,毫秒,默认 0。交错入场就靠它错开 */
31
+ delay?: number;
32
+ /**
33
+ * 缓动函数。白名单:
34
+ * linear / ease / ease-in / ease-out / ease-in-out / step-start / step-end /
35
+ * cubic-bezier(x, x, x, x)。默认 `ease`
36
+ */
37
+ easing?: string;
38
+ /** 播放次数,默认 1;`'infinite'` 表示无限循环(呼吸、扫光这类) */
39
+ iteration?: number | 'infinite';
40
+ /** 播放方向,默认 `normal` */
41
+ direction?: 'normal' | 'reverse' | 'alternate' | 'alternate-reverse';
42
+ /** 填充模式,默认 `both`(入场动画必须保留终态,否则播完会弹回初始值) */
43
+ fill?: 'none' | 'forwards' | 'backwards' | 'both';
44
+ }
45
+ /**
46
+ * 一组关键帧:偏移量 → 样式。
47
+ *
48
+ * 偏移量只接受 `'from'` / `'to'` / `'0%'` 这三种写法;
49
+ * 样式仍要过 style 白名单,写了白名单外的属性会被丢弃。
50
+ */
51
+ type DslKeyframe = Record<string, DslStyle>;
18
52
  interface DslActionBase {
19
53
  type: string;
20
54
  }
@@ -65,6 +99,15 @@ interface DslNode {
65
99
  /** 表达式,为假时整个节点不渲染 */
66
100
  visibleWhen?: string;
67
101
  action?: DslAction;
102
+ /** 入场 / 循环动画 */
103
+ animate?: DslAnimate;
104
+ /**
105
+ * 鼠标悬停时叠加的样式。
106
+ *
107
+ * **只会生成 CSS 规则,不进行内联**——行内样式没有 `:hover` 这个东西,
108
+ * 想在行内做只能靠 JS 监听鼠标事件,那既要框架壳配合、又天然丢触屏。
109
+ */
110
+ hoverStyle?: DslStyle;
68
111
  /** box / flex / countdown */
69
112
  children?: DslNode[];
70
113
  /** repeat / tabs:数组来源表达式 */
@@ -86,6 +129,14 @@ interface DslNode {
86
129
  text?: string;
87
130
  /** countdown */
88
131
  to?: string;
132
+ /**
133
+ * countdown 的相对时长(毫秒)。
134
+ *
135
+ * 从**该视图展示那一刻**起算,「每次打开重新倒 5 分钟」这类写法用它。
136
+ * 与 `to` 同时存在时 `to` 优先(绝对时间更明确)。
137
+ * 起算时间戳钉在运行时里,重算渲染树不会把剩余时间刷回去。
138
+ */
139
+ duration?: number;
89
140
  precision?: 's' | 'cs';
90
141
  as?: string;
91
142
  format?: string;
@@ -95,6 +146,15 @@ interface DslNode {
95
146
  type DslClosePosition = 'top-right' | 'top-left' | 'top-center' | 'bottom-right' | 'bottom-left' | 'bottom-center';
96
147
  interface DslCloseButton {
97
148
  show?: boolean;
149
+ /**
150
+ * 点它做什么。
151
+ *
152
+ * 不配就是默认行为「关掉当前这一层」(多视图里等于弹栈退回上一层,单视图即整体关闭)。
153
+ * 配了就**只**执行这个动作、不再自动关层 —— 「点 ✕ 先弹二次挽留」正是靠它:
154
+ * `{ "type": "open", "view": "confirm", "mode": "stack" }`。
155
+ * 想在动作里顺带关掉,自己写 `close` / `closeAll` 进去。
156
+ */
157
+ action?: DslAction;
98
158
  position?: DslClosePosition;
99
159
  /** [x, y],负值把按钮移到弹窗外面 */
100
160
  offset?: [DslLength, DslLength];
@@ -102,6 +162,10 @@ interface DslCloseButton {
102
162
  icon?: string;
103
163
  image?: string;
104
164
  style?: DslStyle;
165
+ /** 悬停样式,只生成 CSS 规则 */
166
+ hoverStyle?: DslStyle;
167
+ /** 关闭按钮自己的动画(如延迟淡入,先让用户看内容) */
168
+ animate?: DslAnimate;
105
169
  }
106
170
  interface DslStage {
107
171
  width?: DslLength;
@@ -110,8 +174,30 @@ interface DslStage {
110
174
  layout?: 'absolute' | 'flow';
111
175
  mask?: boolean;
112
176
  maskClosable?: boolean;
177
+ /**
178
+ * 点遮罩做什么。
179
+ *
180
+ * 不配就是默认行为「关掉当前这一层」。配了就执行这个动作而不关层
181
+ * (原页面点遮罩弹二次挽留就是这么来的)。
182
+ *
183
+ * 与 `maskClosable` 各管一件事、不冲突:`maskClosable: false` 时遮罩根本不响应点击,
184
+ * 这个动作也不会执行;`maskAction` 只决定「点了之后做什么」。
185
+ */
186
+ maskAction?: DslAction;
113
187
  closeButton?: DslCloseButton;
188
+ /**
189
+ * 遮罩自身样式,走 style 白名单。
190
+ *
191
+ * 内置默认是 `rgba(0, 0, 0, 0.55)` 纯色,这里配的会**覆盖**默认值
192
+ * (常用 `background` 改深浅、`backdropFilter: 'blur(4px)'` 加毛玻璃)。
193
+ * `maskAnimate` 生成的 `animation` 仍排在最后,不会被这里的配置盖掉。
194
+ */
195
+ maskStyle?: DslStyle;
114
196
  style?: DslStyle;
197
+ /** 舞台入场动画(弹窗主体的弹入 / 放大) */
198
+ animate?: DslAnimate;
199
+ /** 遮罩入场动画(一般是淡入,和舞台分开配才能各走各的时长) */
200
+ maskAnimate?: DslAnimate;
115
201
  /** 曝光埋点:数据就绪、视图真正展示时触发一次 */
116
202
  onShow?: DslAction;
117
203
  /** 关闭埋点:点 X、点遮罩、close 动作、被 replace 顶掉都会触发 */
@@ -145,6 +231,13 @@ interface Dsl extends Partial<DslView> {
145
231
  derived?: Record<string, DslDerived>;
146
232
  views?: Record<string, DslView>;
147
233
  entry?: string;
234
+ /**
235
+ * 关键帧库,`{ 名字: { 偏移量: 样式 } }`。
236
+ *
237
+ * 全配置共用一份(多视图之间也共用),`animate.name` 引用这里的名字。
238
+ * 名字只能是 `^[a-zA-Z][a-zA-Z0-9_-]{0,31}$`——它会被拼进 CSS 文本,必须可控。
239
+ */
240
+ keyframes?: Record<string, DslKeyframe>;
148
241
  }
149
242
  /** 已经算好的行内样式,键是 CSS 属性名(驼峰),值是最终字符串 */
150
243
  type CssStyle = Record<string, string>;
@@ -198,8 +291,15 @@ interface RenderLayer {
198
291
  name: string;
199
292
  type: DslViewType;
200
293
  isTop: boolean;
201
- /** 画不画遮罩。只有栈顶的弹窗层才为 true */
294
+ /**
295
+ * 画不画遮罩元素。弹窗层恒为 true(`stage.mask: false` 除外),**包括非栈顶的层**。
296
+ *
297
+ * 遮挡效果由 `maskStyle` 控制:非栈顶层的底色是 transparent,实际遮挡永远只有栈顶那层
298
+ * (两层实心遮罩叠加会明显变黑)。元素本身常驻是刻意的 —— 按 isTop 增删这个 DOM 节点,
299
+ * 会让下层的 `maskAnimate` 在退回栈顶时重播一遍,表现为「关掉二次挽留层,背景闪了一下」。
300
+ */
202
301
  mask: boolean;
302
+ /** 点遮罩的回调。只有栈顶那层才有:非栈顶层整层 pointerEvents: none,点不到 */
203
303
  onMaskClick?: () => void;
204
304
  layerStyle: CssStyle;
205
305
  maskStyle: CssStyle;
@@ -227,6 +327,20 @@ interface RenderTree {
227
327
  countdownEndTimes: number[];
228
328
  /** 只要有一个倒计时要厘秒,整体就按厘秒刷 */
229
329
  countdownPrecision: 's' | 'cs';
330
+ /**
331
+ * 这份配置要注入页面的全部 CSS 文本(keyframes 定义 + hover 规则 + reduced-motion 兜底)。
332
+ *
333
+ * 没有动画也没有 hover 时是**空串**,此时框架壳不该建 `<style>` 元素。
334
+ * 内容已经过安全过滤,壳直接塞进 `<style>` 即可,**不得在壳里做任何生成或判断**。
335
+ */
336
+ css: string;
337
+ /**
338
+ * 根容器类名,形如 `dsl-renderer dsl-r3`。
339
+ *
340
+ * 后半截是这个运行时实例的作用域前缀,`css` 里的每条规则都挂在它下面——
341
+ * 同一个页面上出现多份物料时,各自的 hover 规则不会串台。
342
+ */
343
+ rootClassName: string;
230
344
  }
231
345
  type DslSource = (params: Record<string, unknown>, user: Record<string, unknown>) => unknown | Promise<unknown>;
232
346
  type DslHandler = (params: Record<string, unknown>) => void;
@@ -342,4 +456,4 @@ interface RuntimeOptions {
342
456
  emit?: RuntimeEmit;
343
457
  }
344
458
 
345
- export type { DslHandler as A, DslInteractionTrigger as B, CssStyle as C, Dsl as D, DslInteractionEvent as E, RuntimeEvents as F, RuntimeEventName as G, RuntimeEmit as H, RuntimeOptions as R, RenderTree as a, DslView as b, DslStyle as c, DslViewType as d, DslNodeType as e, DslLength as f, DslRect as g, DslActionBase as h, DslNavigateAction as i, DslCloseAction as j, DslCloseAllAction as k, DslOpenAction as l, DslSetStateAction as m, DslTrackAction as n, DslCallAction as o, DslSequenceAction as p, DslAction as q, DslNode as r, DslClosePosition as s, DslCloseButton as t, DslStage as u, DslSourceRef as v, DslDerived as w, RenderElement as x, RenderLayer as y, DslSource as z };
459
+ 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yaoxiu/marketing-dsl",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "description": "营销弹窗 DSL 解释器核心,纯逻辑无框架依赖",
6
6
  "license": "MIT",