@ant-design/x-markdown-mini 0.1.0-beta.0 → 0.1.0-beta.1

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.
Files changed (48) hide show
  1. package/dist/components/Markdown/index.acss +65 -7
  2. package/dist/components/MiniNodeRenderer/index.acss +19 -4
  3. package/dist/components/MiniNodeRenderer/index.axml +109 -29
  4. package/dist/components/MiniNodeRenderer/index.js +12 -0
  5. package/dist/components/MiniNodeRenderer/index.sjs +48 -0
  6. package/dist/es/Markdown/index.acss +65 -7
  7. package/dist/es/MiniNodeRenderer/index.acss +19 -4
  8. package/dist/es/MiniNodeRenderer/index.axml +109 -29
  9. package/dist/es/MiniNodeRenderer/index.js +12 -0
  10. package/dist/es/MiniNodeRenderer/index.sjs +48 -0
  11. package/dist/index.d.mts +39 -1
  12. package/dist/index.d.ts +39 -1
  13. package/dist/index.js +134 -56
  14. package/dist/index.mjs +134 -56
  15. package/dist/miniprogram_dist/components/Markdown/index.json +1 -1
  16. package/dist/miniprogram_dist/components/Markdown/index.wxss +65 -7
  17. package/dist/miniprogram_dist/components/MiniNodeRenderer/index.js +12 -0
  18. package/dist/miniprogram_dist/components/MiniNodeRenderer/index.json +1 -1
  19. package/dist/miniprogram_dist/components/MiniNodeRenderer/index.wxml +98 -21
  20. package/dist/miniprogram_dist/components/MiniNodeRenderer/index.wxs +41 -0
  21. package/dist/miniprogram_dist/components/MiniNodeRenderer/index.wxss +13 -2
  22. package/dist/miniprogram_dist/es/Markdown/index.json +1 -1
  23. package/dist/miniprogram_dist/es/Markdown/index.wxss +65 -7
  24. package/dist/miniprogram_dist/es/MiniNodeRenderer/index.js +12 -0
  25. package/dist/miniprogram_dist/es/MiniNodeRenderer/index.json +1 -1
  26. package/dist/miniprogram_dist/es/MiniNodeRenderer/index.wxml +98 -21
  27. package/dist/miniprogram_dist/es/MiniNodeRenderer/index.wxs +41 -0
  28. package/dist/miniprogram_dist/es/MiniNodeRenderer/index.wxss +13 -2
  29. package/dist/miniprogram_dist/index.js +134 -56
  30. package/dist/miniprogram_dist/plugins/CodeHighlight/index.js +13 -5
  31. package/dist/miniprogram_dist/plugins/CodeHighlight/style.wxss +41 -31
  32. package/dist/miniprogram_dist/plugins/Latex/index.js +44 -23
  33. package/dist/miniprogram_dist/plugins/Latex/style.wxss +11 -11
  34. package/dist/miniprogram_dist/shared/flattenInline.js +33 -5
  35. package/dist/plugins/CodeHighlight/index.d.mts +1 -1
  36. package/dist/plugins/CodeHighlight/index.d.ts +1 -1
  37. package/dist/plugins/CodeHighlight/index.js +13 -5
  38. package/dist/plugins/CodeHighlight/index.mjs +13 -5
  39. package/dist/plugins/CodeHighlight/style.acss +41 -31
  40. package/dist/plugins/Latex/index.d.mts +1 -1
  41. package/dist/plugins/Latex/index.d.ts +1 -1
  42. package/dist/plugins/Latex/index.js +44 -23
  43. package/dist/plugins/Latex/index.mjs +47 -23
  44. package/dist/plugins/Latex/style.acss +11 -6
  45. package/dist/shared/flattenInline.js +33 -5
  46. package/dist/{types-CegkonfJ.d.mts → types-BcxGtbQZ.d.mts} +25 -0
  47. package/dist/{types-CegkonfJ.d.ts → types-BcxGtbQZ.d.ts} +25 -0
  48. package/package.json +3 -2
@@ -45,11 +45,47 @@
45
45
 
46
46
  .md-code-block {
47
47
  display: block;
48
- margin: 12rpx 0;
49
48
  padding: 16rpx;
50
49
  background: #f6f8fa;
51
- border-radius: 8rpx;
52
50
  overflow-x: auto;
51
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
52
+ font-size: 26rpx;
53
+ line-height: 1.6;
54
+ white-space: pre;
55
+ }
56
+
57
+ /* code & table cards share a captioned header bar with an icon copy button */
58
+ .md-codeblock,
59
+ .md-tableblock {
60
+ margin: 16rpx 0;
61
+ border: 2rpx solid #eceef1;
62
+ border-radius: 16rpx;
63
+ overflow: hidden;
64
+ }
65
+ .md-codeblock-bar,
66
+ .md-tableblock-bar {
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: space-between;
70
+ height: 64rpx;
71
+ padding: 0 24rpx;
72
+ background: #eceef2;
73
+ font-size: 24rpx;
74
+ color: #4e5969;
75
+ }
76
+ .md-codeblock-lang {
77
+ text-transform: uppercase;
78
+ letter-spacing: 1rpx;
79
+ font-weight: 600;
80
+ color: #1d2129;
81
+ }
82
+ .md-tableblock-title {
83
+ font-weight: 600;
84
+ color: #1d2129;
85
+ }
86
+ .md-copy-icon {
87
+ width: 32rpx;
88
+ height: 32rpx;
53
89
  }
54
90
 
55
91
  .md-code {
@@ -78,6 +114,8 @@
78
114
  .md-img {
79
115
  display: block;
80
116
  max-width: 100%;
117
+ margin: 16rpx 0;
118
+ border-radius: 12rpx;
81
119
  }
82
120
 
83
121
  .md-hr {
@@ -87,17 +125,37 @@
87
125
  margin: 16rpx 0;
88
126
  }
89
127
 
90
- .md-table {
128
+ /* table body scrolls horizontally inside the .md-tableblock card */
129
+ .md-table-scroll {
91
130
  display: block;
92
131
  width: 100%;
93
- margin: 12rpx 0;
94
- border-collapse: collapse;
132
+ overflow: hidden;
133
+ white-space: nowrap;
95
134
  }
135
+ .md-table {
136
+ display: table;
137
+ table-layout: auto;
138
+ width: max-content;
139
+ min-width: 100%;
140
+ border-collapse: separate;
141
+ border-spacing: 0;
142
+ font-size: 26rpx;
143
+ }
144
+ .md-tr { display: table-row; }
96
145
  .md-th, .md-td {
97
146
  display: table-cell;
98
- padding: 8rpx 12rpx;
99
- border: 1rpx solid #e5e5e5;
147
+ padding: 12rpx 20rpx;
148
+ max-width: 480rpx;
149
+ border-right: 2rpx solid #e5e5e5;
150
+ border-bottom: 2rpx solid #e5e5e5;
151
+ text-align: left;
152
+ vertical-align: top;
153
+ word-break: break-word;
154
+ white-space: normal;
100
155
  }
101
156
  .md-th { background: #fafafa; font-weight: 600; }
157
+ .md-tr .md-th:last-child,
158
+ .md-tr .md-td:last-child { border-right: none; }
159
+ .md-table .md-tr:last-child .md-td { border-bottom: none; }
102
160
 
103
161
  .md-html { display: block; }
@@ -1,11 +1,26 @@
1
1
  .md-animate-block {
2
2
  animation-fill-mode: forwards;
3
3
  animation-name: md-block-appear;
4
- animation-timing-function: var(--md-animation-timing, ease-in);
5
- animation-duration: var(--md-animation-duration, 300ms);
4
+ animation-timing-function: ease-in;
5
+ animation-duration: 300ms;
6
6
  }
7
7
 
8
+ /* 纯透明度淡入:不做 translateY 位移,避免流式逐字重渲染时块体上下抖动。 */
8
9
  @keyframes md-block-appear {
9
- 0% { opacity: 0; transform: translateY(4rpx); }
10
- 100% { opacity: 1; transform: translateY(0); }
10
+ from { opacity: 0.3; }
11
+ to { opacity: 1; }
12
+ }
13
+
14
+ /* 流式逐字淡入:每个新字符从透明渐显,已显示的字符(按下标复用)不会重播。
15
+ Alipay 不能嵌套 <text>,包裹层用 inline view 维持文本流。 */
16
+ .md-anim-text {
17
+ display: inline;
18
+ }
19
+ .md-anim-char {
20
+ opacity: 0;
21
+ animation: md-char-in 360ms ease-out forwards;
22
+ }
23
+ @keyframes md-char-in {
24
+ from { opacity: 0; }
25
+ to { opacity: 1; }
11
26
  }
@@ -1,9 +1,23 @@
1
1
  <import-sjs from="./index.sjs" name="u" />
2
2
 
3
3
  <block a:for="{{nodes}}" a:for-item="node" a:for-index="i" a:key="i">
4
- <!-- text leaf -->
4
+ <!-- text leaf: 流式时逐字淡入(Alipay 不能嵌套 <text>,用 inline <view> 包裹字符 <text>,
5
+ 按下标 key 复用,只有新字触发动画);非流式时整段渲染为单个 <text>。 -->
6
+ <view
7
+ a:if="{{u.isText(node.name) && animation}}"
8
+ class="md-anim-text {{u.classOf(node)}}"
9
+ >
10
+ <text
11
+ a:for="{{u.charsOf(node)}}"
12
+ a:for-item="ch"
13
+ a:for-index="ci"
14
+ a:key="ci"
15
+ class="md-anim-char"
16
+ selectable="{{selectable}}"
17
+ >{{ch}}</text>
18
+ </view>
5
19
  <text
6
- a:if="{{u.isText(node.name)}}"
20
+ a:elif="{{u.isText(node.name)}}"
7
21
  class="{{u.classOf(node)}}"
8
22
  selectable="{{selectable}}"
9
23
  >{{u.valueOf(node)}}</text>
@@ -26,34 +40,101 @@
26
40
  onAppear="_appear"
27
41
  />
28
42
 
29
- <!-- pre: scrollable code container -->
30
- <scroll-view
43
+ <!-- pre: code block with language label + copy toolbar, scrollable body -->
44
+ <view
31
45
  a:elif="{{u.isPre(node.name)}}"
32
- scroll-x
33
- class="{{u.classOf(node)}}"
34
- style="{{u.styleOf(node)}}"
35
- data-data="{{node}}"
36
- catchTap="_tap"
46
+ class="md-codeblock {{node.animate ? 'md-animate-block' : ''}}"
37
47
  >
38
- <mini-node-renderer
39
- a:if="{{node.children}}"
40
- nodes="{{node.children}}"
41
- selectable="{{selectable}}"
42
- animation="{{false}}"
43
- slotComponents="{{slotComponents}}"
44
- onTap="onTap"
45
- onAppear="onAppear"
46
- >
47
- <slot slot-scope="prop" data="{{prop.data}}" />
48
- </mini-node-renderer>
49
- </scroll-view>
48
+ <view a:if="{{node.header}}" class="md-codeblock-bar">
49
+ <block a:for="{{node.header}}" a:for-item="h" a:for-index="hi" a:key="hi">
50
+ <text a:if="{{u.isText(h.name)}}" class="{{u.classOf(h)}}">{{u.valueOf(h)}}</text>
51
+ <image
52
+ a:elif="{{u.isCopy(h.name)}}"
53
+ class="{{u.classOf(h) || 'md-copy-icon'}}"
54
+ src="https://mdn.alipayobjects.com/huamei_y8xg5f/afts/img/JXzqSoHDzm4AAAAAHpAAAAgADuJhAQFr/original"
55
+ data-copy="{{u.copyOf(h)}}"
56
+ catchTap="_copy"
57
+ />
58
+ <view a:else class="{{u.classOf(h)}}" style="{{u.styleOf(h)}}">
59
+ <mini-node-renderer
60
+ a:if="{{h.children}}"
61
+ nodes="{{h.children}}"
62
+ selectable="{{selectable}}"
63
+ animation="{{false}}"
64
+ slotComponents="{{slotComponents}}"
65
+ onTap="onTap"
66
+ >
67
+ <slot slot-scope="prop" data="{{prop.data}}" />
68
+ </mini-node-renderer>
69
+ </view>
70
+ </block>
71
+ </view>
72
+ <scroll-view scroll-x class="md-code-block">
73
+ <mini-node-renderer
74
+ a:if="{{node.children}}"
75
+ nodes="{{node.children}}"
76
+ selectable="{{selectable}}"
77
+ animation="{{false}}"
78
+ slotComponents="{{slotComponents}}"
79
+ onTap="onTap"
80
+ onAppear="onAppear"
81
+ >
82
+ <slot slot-scope="prop" data="{{prop.data}}" />
83
+ </mini-node-renderer>
84
+ </scroll-view>
85
+ </view>
86
+
87
+ <!-- table: caption + copy toolbar card, horizontal-scroll CSS table (columns kept aligned) -->
88
+ <view
89
+ a:elif="{{u.isTable(node.name)}}"
90
+ class="md-tableblock {{node.animate ? 'md-animate-block' : ''}}"
91
+ >
92
+ <view a:if="{{node.header}}" class="md-tableblock-bar">
93
+ <block a:for="{{node.header}}" a:for-item="h" a:for-index="hi" a:key="hi">
94
+ <text a:if="{{u.isText(h.name)}}" class="{{u.classOf(h)}}">{{u.valueOf(h)}}</text>
95
+ <image
96
+ a:elif="{{u.isCopy(h.name)}}"
97
+ class="{{u.classOf(h) || 'md-copy-icon'}}"
98
+ src="https://mdn.alipayobjects.com/huamei_y8xg5f/afts/img/JXzqSoHDzm4AAAAAHpAAAAgADuJhAQFr/original"
99
+ data-copy="{{u.copyOf(h)}}"
100
+ catchTap="_copy"
101
+ />
102
+ <view a:else class="{{u.classOf(h)}}" style="{{u.styleOf(h)}}">
103
+ <mini-node-renderer
104
+ a:if="{{h.children}}"
105
+ nodes="{{h.children}}"
106
+ selectable="{{selectable}}"
107
+ animation="{{false}}"
108
+ slotComponents="{{slotComponents}}"
109
+ onTap="onTap"
110
+ >
111
+ <slot slot-scope="prop" data="{{prop.data}}" />
112
+ </mini-node-renderer>
113
+ </view>
114
+ </block>
115
+ </view>
116
+ <scroll-view scroll-x class="md-table-scroll">
117
+ <view class="md-table">
118
+ <mini-node-renderer
119
+ a:if="{{node.children}}"
120
+ nodes="{{node.children}}"
121
+ selectable="{{selectable}}"
122
+ animation="{{false}}"
123
+ slotComponents="{{slotComponents}}"
124
+ onTap="onTap"
125
+ onAppear="onAppear"
126
+ >
127
+ <slot slot-scope="prop" data="{{prop.data}}" />
128
+ </mini-node-renderer>
129
+ </view>
130
+ </scroll-view>
131
+ </view>
50
132
 
51
133
  <!-- anchor: kept interactive, children are already flattened text runs -->
52
- <text
134
+ <view
53
135
  a:elif="{{node.name === 'a'}}"
54
136
  class="{{u.classOf(node)}}"
55
137
  style="{{u.styleOf(node)}}"
56
- selectable="{{selectable}}"
57
138
  data-data="{{node}}"
58
139
  catchTap="_tap"
59
140
  >
@@ -64,14 +145,13 @@
64
145
  a:key="ci"
65
146
  class="{{u.classOf(c)}}"
66
147
  >{{u.valueOf(c)}}</text>
67
- </text>
148
+ </view>
68
149
 
69
- <!-- inline tag rendered as <text>: children are flat text runs -->
70
- <text
71
- a:elif="{{u.isInline(node.name)}}"
150
+ <!-- inline tag rendered as a flat container because Alipay SDK 2.x rejects nested <text>. -->
151
+ <view
152
+ a:elif="{{u.isInline(node.name) && !u.isRich(node)}}"
72
153
  class="{{u.classOf(node)}}"
73
154
  style="{{u.styleOf(node)}}"
74
- selectable="{{selectable}}"
75
155
  data-data="{{node}}"
76
156
  catchTap="_tap"
77
157
  >
@@ -82,7 +162,7 @@
82
162
  a:key="ci"
83
163
  class="{{u.classOf(c)}}"
84
164
  >{{u.valueOf(c)}}</text>
85
- </text>
165
+ </view>
86
166
 
87
167
  <!-- custom component: hand off to host scoped slot, keyed by node.tag -->
88
168
  <slot
@@ -16,6 +16,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
16
16
  // src/components/alipay/MiniNodeRenderer/index.ts
17
17
  var MiniNodeRenderer_exports = {};
18
18
  module.exports = __toCommonJS(MiniNodeRenderer_exports);
19
+ function copyToClipboard(text) {
20
+ if (!text) return;
21
+ my.setClipboard({
22
+ text,
23
+ success: () => my.showToast({ content: "\u5DF2\u590D\u5236", duration: 1200 }),
24
+ fail: () => my.showToast({ content: "\u590D\u5236\u5931\u8D25" })
25
+ });
26
+ }
19
27
  var defaultProps = {
20
28
  nodes: [],
21
29
  selectable: true,
@@ -32,6 +40,10 @@ Component({
32
40
  _appear(e) {
33
41
  var _a, _b;
34
42
  (_b = (_a = this.props).onAppear) == null ? void 0 : _b.call(_a, e);
43
+ },
44
+ _copy(e) {
45
+ const ds = e && e.currentTarget && e.currentTarget.dataset;
46
+ copyToClipboard(ds && ds.copy || "");
35
47
  }
36
48
  }
37
49
  });
@@ -13,6 +13,23 @@ function isBr(name) { return name === 'br'; }
13
13
  function isImg(name) { return name === 'img'; }
14
14
  function isHr(name) { return name === 'hr'; }
15
15
  function isPre(name) { return name === 'pre'; }
16
+ function isTable(name) { return name === 'table'; }
17
+ function isCopy(name) { return name === 'copy-button'; }
18
+ function copyOf(node) {
19
+ var attrs = node.attrs || {};
20
+ return attrs['data-copy'] || '';
21
+ }
22
+ // 富内联节点(如 KaTeX 公式容器)含有元素子节点,必须走递归 <view> 路径
23
+ // 渲染,保留嵌套结构与 style;普通已扁平化的内联只有 text/br 子节点。
24
+ function isRich(node) {
25
+ var ch = node.children;
26
+ if (!ch) return false;
27
+ for (var i = 0; i < ch.length; i++) {
28
+ var nm = ch[i].name;
29
+ if (nm !== 'text' && nm !== 'br') return true;
30
+ }
31
+ return false;
32
+ }
16
33
  function isSlot(name, slotComponents) {
17
34
  return !!slotComponents && slotComponents.indexOf(name) > -1;
18
35
  }
@@ -26,6 +43,26 @@ function classOf(node) {
26
43
  return cls;
27
44
  }
28
45
 
46
+ // 把文本拆成「按 code point 安全」的字符数组(不劈坏 emoji/组合 surrogate pair),
47
+ // 供流式逐字淡入:每个字符渲染成独立 <text>,按下标 key 复用,只有新字符会触发淡入动画。
48
+ function charsOf(node) {
49
+ var v = (node.attrs || {}).value || '';
50
+ var out = [];
51
+ var i = 0;
52
+ var n = v.length;
53
+ while (i < n) {
54
+ var code = v.charCodeAt(i);
55
+ if (code >= 55296 && code <= 56319 && i + 1 < n) {
56
+ out.push(v.charAt(i) + v.charAt(i + 1));
57
+ i += 2;
58
+ } else {
59
+ out.push(v.charAt(i));
60
+ i += 1;
61
+ }
62
+ }
63
+ return out;
64
+ }
65
+
29
66
  function styleOf(node) {
30
67
  var attrs = node.attrs || {};
31
68
  return attrs.style || '';
@@ -36,6 +73,11 @@ function srcOf(node) {
36
73
  return attrs.src || '';
37
74
  }
38
75
 
76
+ function langOf(node) {
77
+ var attrs = node.attrs || {};
78
+ return attrs.lang || '';
79
+ }
80
+
39
81
  function altOf(node) {
40
82
  var attrs = node.attrs || {};
41
83
  return attrs.alt || '';
@@ -54,10 +96,16 @@ export default {
54
96
  isImg: isImg,
55
97
  isHr: isHr,
56
98
  isPre: isPre,
99
+ isTable: isTable,
100
+ isCopy: isCopy,
101
+ copyOf: copyOf,
102
+ isRich: isRich,
57
103
  isSlot: isSlot,
58
104
  classOf: classOf,
105
+ charsOf: charsOf,
59
106
  styleOf: styleOf,
60
107
  srcOf: srcOf,
108
+ langOf: langOf,
61
109
  altOf: altOf,
62
110
  valueOf: valueOf,
63
111
  };
@@ -45,11 +45,47 @@
45
45
 
46
46
  .md-code-block {
47
47
  display: block;
48
- margin: 12rpx 0;
49
48
  padding: 16rpx;
50
49
  background: #f6f8fa;
51
- border-radius: 8rpx;
52
50
  overflow-x: auto;
51
+ font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
52
+ font-size: 26rpx;
53
+ line-height: 1.6;
54
+ white-space: pre;
55
+ }
56
+
57
+ /* code & table cards share a captioned header bar with an icon copy button */
58
+ .md-codeblock,
59
+ .md-tableblock {
60
+ margin: 16rpx 0;
61
+ border: 2rpx solid #eceef1;
62
+ border-radius: 16rpx;
63
+ overflow: hidden;
64
+ }
65
+ .md-codeblock-bar,
66
+ .md-tableblock-bar {
67
+ display: flex;
68
+ align-items: center;
69
+ justify-content: space-between;
70
+ height: 64rpx;
71
+ padding: 0 24rpx;
72
+ background: #eceef2;
73
+ font-size: 24rpx;
74
+ color: #4e5969;
75
+ }
76
+ .md-codeblock-lang {
77
+ text-transform: uppercase;
78
+ letter-spacing: 1rpx;
79
+ font-weight: 600;
80
+ color: #1d2129;
81
+ }
82
+ .md-tableblock-title {
83
+ font-weight: 600;
84
+ color: #1d2129;
85
+ }
86
+ .md-copy-icon {
87
+ width: 32rpx;
88
+ height: 32rpx;
53
89
  }
54
90
 
55
91
  .md-code {
@@ -78,6 +114,8 @@
78
114
  .md-img {
79
115
  display: block;
80
116
  max-width: 100%;
117
+ margin: 16rpx 0;
118
+ border-radius: 12rpx;
81
119
  }
82
120
 
83
121
  .md-hr {
@@ -87,17 +125,37 @@
87
125
  margin: 16rpx 0;
88
126
  }
89
127
 
90
- .md-table {
128
+ /* table body scrolls horizontally inside the .md-tableblock card */
129
+ .md-table-scroll {
91
130
  display: block;
92
131
  width: 100%;
93
- margin: 12rpx 0;
94
- border-collapse: collapse;
132
+ overflow: hidden;
133
+ white-space: nowrap;
95
134
  }
135
+ .md-table {
136
+ display: table;
137
+ table-layout: auto;
138
+ width: max-content;
139
+ min-width: 100%;
140
+ border-collapse: separate;
141
+ border-spacing: 0;
142
+ font-size: 26rpx;
143
+ }
144
+ .md-tr { display: table-row; }
96
145
  .md-th, .md-td {
97
146
  display: table-cell;
98
- padding: 8rpx 12rpx;
99
- border: 1rpx solid #e5e5e5;
147
+ padding: 12rpx 20rpx;
148
+ max-width: 480rpx;
149
+ border-right: 2rpx solid #e5e5e5;
150
+ border-bottom: 2rpx solid #e5e5e5;
151
+ text-align: left;
152
+ vertical-align: top;
153
+ word-break: break-word;
154
+ white-space: normal;
100
155
  }
101
156
  .md-th { background: #fafafa; font-weight: 600; }
157
+ .md-tr .md-th:last-child,
158
+ .md-tr .md-td:last-child { border-right: none; }
159
+ .md-table .md-tr:last-child .md-td { border-bottom: none; }
102
160
 
103
161
  .md-html { display: block; }
@@ -1,11 +1,26 @@
1
1
  .md-animate-block {
2
2
  animation-fill-mode: forwards;
3
3
  animation-name: md-block-appear;
4
- animation-timing-function: var(--md-animation-timing, ease-in);
5
- animation-duration: var(--md-animation-duration, 300ms);
4
+ animation-timing-function: ease-in;
5
+ animation-duration: 300ms;
6
6
  }
7
7
 
8
+ /* 纯透明度淡入:不做 translateY 位移,避免流式逐字重渲染时块体上下抖动。 */
8
9
  @keyframes md-block-appear {
9
- 0% { opacity: 0; transform: translateY(4rpx); }
10
- 100% { opacity: 1; transform: translateY(0); }
10
+ from { opacity: 0.3; }
11
+ to { opacity: 1; }
12
+ }
13
+
14
+ /* 流式逐字淡入:每个新字符从透明渐显,已显示的字符(按下标复用)不会重播。
15
+ Alipay 不能嵌套 <text>,包裹层用 inline view 维持文本流。 */
16
+ .md-anim-text {
17
+ display: inline;
18
+ }
19
+ .md-anim-char {
20
+ opacity: 0;
21
+ animation: md-char-in 360ms ease-out forwards;
22
+ }
23
+ @keyframes md-char-in {
24
+ from { opacity: 0; }
25
+ to { opacity: 1; }
11
26
  }