@tuya-miniapp/smart-ui 2.13.4 → 2.13.5-beta-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/README-zh_CN.md +8 -0
- package/dist/picker/index.js +172 -6
- package/dist/picker/index.wxml +2 -1
- package/dist/picker-column/index.js +11 -1
- package/dist/picker-column/index.wxml +12 -10
- package/dist/picker-column/index.wxs +10 -3
- package/lib/picker/index.js +179 -5
- package/lib/picker/index.wxml +2 -1
- package/lib/picker-column/index.js +11 -1
- package/lib/picker-column/index.wxml +12 -10
- package/lib/picker-column/index.wxs +10 -3
- package/package.json +1 -1
package/README-zh_CN.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* @Author: mjh
|
|
3
|
+
* @Date: 2026-04-01 14:55:38
|
|
4
|
+
* @LastEditors: mjh
|
|
5
|
+
* @LastEditTime: 2026-08-24 11:44:37
|
|
6
|
+
* @Description:
|
|
7
|
+
-->
|
|
1
8
|
[English](./README.md) | 简体中文
|
|
2
9
|
|
|
3
10
|
# @tuya-miniapp/smart-ui
|
|
@@ -78,6 +85,7 @@ yarn dev
|
|
|
78
85
|
|
|
79
86
|
> 本项目遵从 [Angular Style Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153)
|
|
80
87
|
|
|
88
|
+
|
|
81
89
|
[官网更新日志](https://developer.tuya.com/material/smartui?comId=help-changelog&appType=miniapp)
|
|
82
90
|
|
|
83
91
|
## 开源协议
|
package/dist/picker/index.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { SmartComponent } from '../common/component';
|
|
2
2
|
import { pickerProps } from './shared';
|
|
3
|
+
// 超过该数量且非 loop 的列启用「窗口化」:只把当前位置附近的一段数据交给子 picker-column,
|
|
4
|
+
// 其余保存在逻辑层实例属性中,滚动停稳后再回中,避免把超长数组整份 setData 到渲染层 / 子组件。
|
|
5
|
+
const WINDOW_THRESHOLD = 2000;
|
|
6
|
+
const WINDOW_SIZE = 1000;
|
|
7
|
+
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
3
8
|
SmartComponent({
|
|
4
9
|
classes: ['hairline-class', 'active-class', 'toolbar-class', 'column-class'],
|
|
5
10
|
props: Object.assign(Object.assign({}, pickerProps), { valueKey: {
|
|
@@ -16,6 +21,12 @@ SmartComponent({
|
|
|
16
21
|
value: 0,
|
|
17
22
|
}, activeIndex: {
|
|
18
23
|
type: null,
|
|
24
|
+
observer() {
|
|
25
|
+
// 受控模式下 activeIndex(全局)变化时,若存在窗口化列需以新位置重建窗口
|
|
26
|
+
if (this._hasWindow) {
|
|
27
|
+
this.buildRenderColumns();
|
|
28
|
+
}
|
|
29
|
+
},
|
|
19
30
|
}, unit: {
|
|
20
31
|
type: String,
|
|
21
32
|
value: '',
|
|
@@ -24,6 +35,7 @@ SmartComponent({
|
|
|
24
35
|
value: [],
|
|
25
36
|
observer(columns = []) {
|
|
26
37
|
this.simple = columns.length && !columns[0].values;
|
|
38
|
+
this.buildRenderColumns();
|
|
27
39
|
if (Array.isArray(this.children) && this.children.length) {
|
|
28
40
|
this.setColumns().catch(() => { });
|
|
29
41
|
}
|
|
@@ -31,8 +43,14 @@ SmartComponent({
|
|
|
31
43
|
} }),
|
|
32
44
|
data: {
|
|
33
45
|
animating: false,
|
|
46
|
+
// 交给渲染层 / 子组件的列数据(窗口化列在此仅为切片,非窗口化列为完整数据)
|
|
47
|
+
renderColumns: [],
|
|
34
48
|
},
|
|
35
49
|
beforeCreate() {
|
|
50
|
+
// 每列的窗口状态:{ windowed, start, size, full },保存在逻辑层,不进入 data
|
|
51
|
+
this._windows = [];
|
|
52
|
+
this._hasWindow = false;
|
|
53
|
+
this._repositioning = false;
|
|
36
54
|
Object.defineProperty(this, 'children', {
|
|
37
55
|
get: () => this.selectAllComponents('.smart-picker__column') || [],
|
|
38
56
|
});
|
|
@@ -44,10 +62,103 @@ SmartComponent({
|
|
|
44
62
|
},
|
|
45
63
|
methods: {
|
|
46
64
|
noop() { },
|
|
65
|
+
// 归一化列数据:simple 模式(columns 为一维数组)包一层
|
|
66
|
+
normalizedColumns() {
|
|
67
|
+
const { columns } = this.data;
|
|
68
|
+
if (this.simple) {
|
|
69
|
+
return [{ values: columns }];
|
|
70
|
+
}
|
|
71
|
+
return Array.isArray(columns) ? columns : [];
|
|
72
|
+
},
|
|
73
|
+
// 解析某列的初始全局下标(优先 activeIndex,其次 defaultIndex)
|
|
74
|
+
resolveInitialIndex(column) {
|
|
75
|
+
const { defaultIndex, activeIndex } = this.data;
|
|
76
|
+
const columnActive = column.activeIndex === null || column.activeIndex === undefined
|
|
77
|
+
? activeIndex
|
|
78
|
+
: column.activeIndex;
|
|
79
|
+
let index;
|
|
80
|
+
if (columnActive !== null && columnActive !== undefined) {
|
|
81
|
+
index = columnActive;
|
|
82
|
+
}
|
|
83
|
+
else if (column.defaultIndex !== undefined) {
|
|
84
|
+
index = column.defaultIndex;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
index = defaultIndex;
|
|
88
|
+
}
|
|
89
|
+
const { length } = column.values || [];
|
|
90
|
+
return clamp(index || 0, 0, Math.max(0, length - 1));
|
|
91
|
+
},
|
|
92
|
+
// 判断某列是否需要窗口化
|
|
93
|
+
shouldWindow(column) {
|
|
94
|
+
return (!this.data.loop &&
|
|
95
|
+
!column.loop &&
|
|
96
|
+
Array.isArray(column.values) &&
|
|
97
|
+
column.values.length > WINDOW_THRESHOLD);
|
|
98
|
+
},
|
|
99
|
+
// 基于当前 columns 计算 renderColumns 与每列窗口状态
|
|
100
|
+
buildRenderColumns() {
|
|
101
|
+
const columns = this.normalizedColumns();
|
|
102
|
+
const windows = [];
|
|
103
|
+
let hasWindow = false;
|
|
104
|
+
const renderColumns = columns.map((column) => {
|
|
105
|
+
const values = column.values || [];
|
|
106
|
+
if (!this.shouldWindow(column)) {
|
|
107
|
+
windows.push({ windowed: false, start: 0, size: values.length, full: values });
|
|
108
|
+
return column;
|
|
109
|
+
}
|
|
110
|
+
hasWindow = true;
|
|
111
|
+
const globalIndex = this.resolveInitialIndex(column);
|
|
112
|
+
const size = WINDOW_SIZE;
|
|
113
|
+
const start = clamp(globalIndex - Math.floor(size / 2), 0, Math.max(0, values.length - size));
|
|
114
|
+
const localIndex = globalIndex - start;
|
|
115
|
+
windows.push({ windowed: true, start, size, full: values });
|
|
116
|
+
return Object.assign(Object.assign({}, column), { values: values.slice(start, start + size), defaultIndex: localIndex, activeIndex: localIndex, _windowed: true });
|
|
117
|
+
});
|
|
118
|
+
this._windows = windows;
|
|
119
|
+
this._hasWindow = hasWindow;
|
|
120
|
+
this.setData({ renderColumns });
|
|
121
|
+
},
|
|
122
|
+
// 将窗口化列停稳后回中:使当前项回到窗口中心,纯坐标平移,静止态无感
|
|
123
|
+
recenterColumn(columnIndex) {
|
|
124
|
+
const win = this._windows[columnIndex];
|
|
125
|
+
if (!win || !win.windowed || this._repositioning)
|
|
126
|
+
return;
|
|
127
|
+
const column = this.getColumn(columnIndex);
|
|
128
|
+
if (!column)
|
|
129
|
+
return;
|
|
130
|
+
const localIndex = column.data.currentIndex;
|
|
131
|
+
const globalIndex = win.start + localIndex;
|
|
132
|
+
const { size } = win;
|
|
133
|
+
const newStart = clamp(globalIndex - Math.floor(size / 2), 0, Math.max(0, win.full.length - size));
|
|
134
|
+
if (newStart === win.start)
|
|
135
|
+
return;
|
|
136
|
+
this._repositioning = true;
|
|
137
|
+
win.start = newStart;
|
|
138
|
+
const newLocal = globalIndex - newStart;
|
|
139
|
+
// 同一次 setData 内同时更新切片与 active-index(局部),子组件原子接收,
|
|
140
|
+
// 配合 wxs 对大跳变强制 transition:none,实现无动画的静默回中
|
|
141
|
+
this.setData({
|
|
142
|
+
[`renderColumns[${columnIndex}].values`]: win.full.slice(newStart, newStart + size),
|
|
143
|
+
[`renderColumns[${columnIndex}].activeIndex`]: newLocal,
|
|
144
|
+
}, () => {
|
|
145
|
+
this._repositioning = false;
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
// 局部下标 -> 全局下标
|
|
149
|
+
toGlobalIndex(columnIndex, localIndex) {
|
|
150
|
+
const win = this._windows[columnIndex];
|
|
151
|
+
return win && win.windowed ? win.start + localIndex : localIndex;
|
|
152
|
+
},
|
|
47
153
|
setColumns() {
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
154
|
+
const columns = this.normalizedColumns();
|
|
155
|
+
const stack = columns.map((column, index) => {
|
|
156
|
+
// 窗口化列由 renderColumns(wxml)响应式驱动,不走命令式全量下发
|
|
157
|
+
if (this._windows[index] && this._windows[index].windowed) {
|
|
158
|
+
return Promise.resolve();
|
|
159
|
+
}
|
|
160
|
+
return this.setColumnValues(index, column.values);
|
|
161
|
+
});
|
|
51
162
|
return Promise.all(stack);
|
|
52
163
|
},
|
|
53
164
|
emit(event) {
|
|
@@ -66,6 +177,7 @@ SmartComponent({
|
|
|
66
177
|
}
|
|
67
178
|
},
|
|
68
179
|
onChange(event) {
|
|
180
|
+
const columnIndex = this.simple ? 0 : event.currentTarget.dataset.index;
|
|
69
181
|
if (this.simple) {
|
|
70
182
|
this.$emit('change', {
|
|
71
183
|
picker: this,
|
|
@@ -80,6 +192,8 @@ SmartComponent({
|
|
|
80
192
|
index: event.currentTarget.dataset.index,
|
|
81
193
|
});
|
|
82
194
|
}
|
|
195
|
+
// 停稳后再回中,保证切换发生在静止态
|
|
196
|
+
this.recenterColumn(columnIndex);
|
|
83
197
|
},
|
|
84
198
|
// get column instance by index
|
|
85
199
|
getColumn(index) {
|
|
@@ -92,6 +206,19 @@ SmartComponent({
|
|
|
92
206
|
},
|
|
93
207
|
// set column value by index
|
|
94
208
|
setColumnValue(index, value) {
|
|
209
|
+
const win = this._windows[index];
|
|
210
|
+
if (win && win.windowed) {
|
|
211
|
+
// 窗口化列:在完整数据中按 valueKey 查找全局下标后跳转
|
|
212
|
+
const { valueKey } = this.data;
|
|
213
|
+
for (let i = 0; i < win.full.length; i++) {
|
|
214
|
+
const option = win.full[i];
|
|
215
|
+
const text = option && typeof option === 'object' && valueKey in option ? option[valueKey] : option;
|
|
216
|
+
if (text === value) {
|
|
217
|
+
return this.setColumnIndex(index, i);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return Promise.resolve();
|
|
221
|
+
}
|
|
95
222
|
const column = this.getColumn(index);
|
|
96
223
|
if (column == null) {
|
|
97
224
|
return Promise.reject(new Error('setColumnValue: The corresponding column does not exist'));
|
|
@@ -100,10 +227,27 @@ SmartComponent({
|
|
|
100
227
|
},
|
|
101
228
|
// get column option index by column index
|
|
102
229
|
getColumnIndex(columnIndex) {
|
|
103
|
-
|
|
230
|
+
var _a, _b;
|
|
231
|
+
const local = (_b = (_a = (this.getColumn(columnIndex) || {}).data) === null || _a === void 0 ? void 0 : _a.currentIndex) !== null && _b !== void 0 ? _b : 0;
|
|
232
|
+
return this.toGlobalIndex(columnIndex, local);
|
|
104
233
|
},
|
|
105
234
|
// set column option index by column index
|
|
106
235
|
setColumnIndex(columnIndex, optionIndex) {
|
|
236
|
+
const win = this._windows[columnIndex];
|
|
237
|
+
if (win && win.windowed) {
|
|
238
|
+
// 以目标全局下标为中心重建窗口,子组件通过 active-index 定位
|
|
239
|
+
const { size } = win;
|
|
240
|
+
const globalIndex = clamp(optionIndex, 0, Math.max(0, win.full.length - 1));
|
|
241
|
+
const newStart = clamp(globalIndex - Math.floor(size / 2), 0, Math.max(0, win.full.length - size));
|
|
242
|
+
win.start = newStart;
|
|
243
|
+
this._repositioning = true;
|
|
244
|
+
return this.set({
|
|
245
|
+
[`renderColumns[${columnIndex}].values`]: win.full.slice(newStart, newStart + size),
|
|
246
|
+
[`renderColumns[${columnIndex}].activeIndex`]: globalIndex - newStart,
|
|
247
|
+
}).then(() => {
|
|
248
|
+
this._repositioning = false;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
107
251
|
const column = this.getColumn(columnIndex);
|
|
108
252
|
if (column == null) {
|
|
109
253
|
return Promise.reject(new Error('setColumnIndex: The corresponding column does not exist'));
|
|
@@ -112,15 +256,37 @@ SmartComponent({
|
|
|
112
256
|
},
|
|
113
257
|
// get options of column by index
|
|
114
258
|
getColumnValues(index) {
|
|
259
|
+
const win = this._windows[index];
|
|
260
|
+
if (win && win.windowed) {
|
|
261
|
+
return win.full;
|
|
262
|
+
}
|
|
115
263
|
return (this.children[index] || {}).data.options;
|
|
116
264
|
},
|
|
117
265
|
// set options of column by index
|
|
118
266
|
setColumnValues(index, options, needReset = true) {
|
|
267
|
+
const win = this._windows[index];
|
|
268
|
+
if (win && win.windowed) {
|
|
269
|
+
// 替换完整数据并以头部为中心重建窗口
|
|
270
|
+
win.full = options || [];
|
|
271
|
+
const { size } = win;
|
|
272
|
+
const start = needReset ? 0 : clamp(win.start, 0, Math.max(0, win.full.length - size));
|
|
273
|
+
win.start = start;
|
|
274
|
+
this._repositioning = true;
|
|
275
|
+
return this.set({
|
|
276
|
+
[`renderColumns[${index}].values`]: win.full.slice(start, start + size),
|
|
277
|
+
[`renderColumns[${index}].activeIndex`]: 0,
|
|
278
|
+
}).then(() => {
|
|
279
|
+
this._repositioning = false;
|
|
280
|
+
});
|
|
281
|
+
}
|
|
119
282
|
const column = this.children[index];
|
|
120
283
|
if (column == null) {
|
|
121
284
|
return Promise.reject(new Error('setColumnValues: The corresponding column does not exist'));
|
|
122
285
|
}
|
|
123
|
-
const
|
|
286
|
+
const prevOptions = column.data.options || [];
|
|
287
|
+
// 先比长度(O(1))短路:长度不同必然不同,避免对超长数组做两次 JSON.stringify
|
|
288
|
+
const isSame = prevOptions.length === options.length &&
|
|
289
|
+
JSON.stringify(prevOptions) === JSON.stringify(options);
|
|
124
290
|
if (isSame) {
|
|
125
291
|
return Promise.resolve();
|
|
126
292
|
}
|
|
@@ -141,7 +307,7 @@ SmartComponent({
|
|
|
141
307
|
},
|
|
142
308
|
// get indexes of all columns
|
|
143
309
|
getIndexes() {
|
|
144
|
-
return this.children.map(child => child.data.currentIndex);
|
|
310
|
+
return this.children.map((child, index) => this.toGlobalIndex(index, child.data.currentIndex));
|
|
145
311
|
},
|
|
146
312
|
// set indexes of all columns
|
|
147
313
|
setIndexes(indexes) {
|
package/dist/picker/index.wxml
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
>
|
|
15
15
|
<picker-column
|
|
16
16
|
class="smart-picker__column"
|
|
17
|
-
wx:for="{{
|
|
17
|
+
wx:for="{{ renderColumns }}"
|
|
18
18
|
wx:key="index"
|
|
19
19
|
data-index="{{ index }}"
|
|
20
20
|
animation-time="{{ animationTime }}"
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
active-style="{{ activeStyle }}"
|
|
25
25
|
font-style="{{fontStyle ? fontStyle + ';' + computed.style(item.fontStyle): computed.style(item.fontStyle)}}"
|
|
26
26
|
options="{{ item.values }}"
|
|
27
|
+
windowed="{{ item._windowed || false }}"
|
|
27
28
|
disabled="{{ item.disabled || false }}"
|
|
28
29
|
unit="{{ item.unit || unit }}"
|
|
29
30
|
unit-gap="{{ item.unitGap }}"
|
|
@@ -16,6 +16,11 @@ SmartComponent({
|
|
|
16
16
|
className: String,
|
|
17
17
|
itemHeight: Number,
|
|
18
18
|
disabled: Boolean,
|
|
19
|
+
// 是否为父级窗口化列:为 true 时对大幅度的 active-index 跳变强制关闭过渡(用于停稳回中的静默重定位)
|
|
20
|
+
windowed: {
|
|
21
|
+
type: Boolean,
|
|
22
|
+
value: false,
|
|
23
|
+
},
|
|
19
24
|
visibleItemCount: Number,
|
|
20
25
|
activeStyle: {
|
|
21
26
|
type: String,
|
|
@@ -28,7 +33,12 @@ SmartComponent({
|
|
|
28
33
|
if (!this.data.isInit)
|
|
29
34
|
return;
|
|
30
35
|
this.updateUint(value);
|
|
31
|
-
|
|
36
|
+
// 窗口化列:切片与 activeIndex(局部)在同一批 setData 到达,
|
|
37
|
+
// 此处直接采用新的 activeIndex,使「新切片 + 新下标」原子落地,避免差一帧的错位闪烁
|
|
38
|
+
const nextIndex = this.data.windowed && this.data.activeIndex !== null && this.data.activeIndex !== undefined
|
|
39
|
+
? this.data.activeIndex
|
|
40
|
+
: this.data.currentIndex;
|
|
41
|
+
this.updateCurrentIndex(nextIndex);
|
|
32
42
|
this.updateVisibleOptions();
|
|
33
43
|
},
|
|
34
44
|
},
|
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
id="{{instanceId}}"
|
|
9
9
|
data-isdestroy="{{isDestroy}}"
|
|
10
10
|
data-changeanimation="{{changeAnimation}}"
|
|
11
|
-
data-options="{{options}}"
|
|
12
11
|
data-valuekey="{{valueKey}}"
|
|
13
12
|
data-itemheight="{{itemHeight}}"
|
|
14
13
|
data-visibleitemcount="{{visibleItemCount}}"
|
|
15
14
|
data-activeindex="{{currentIndex}}"
|
|
16
15
|
data-loop="{{loop && options.length > 1}}"
|
|
17
16
|
data-animationtime="{{animationTime}}"
|
|
17
|
+
data-windowed="{{windowed}}"
|
|
18
18
|
isdestroy="{{isDestroy}}"
|
|
19
|
-
options="{{options}}"
|
|
19
|
+
options="{{options}}"
|
|
20
20
|
activeindex="{{currentIndex}}"
|
|
21
21
|
changeanimation="{{changeAnimation}}"
|
|
22
22
|
loop="{{loop && options.length > 1}}"
|
|
@@ -24,15 +24,17 @@
|
|
|
24
24
|
itemheight="{{itemHeight}}"
|
|
25
25
|
visibleitemcount="{{visibleItemCount}}"
|
|
26
26
|
animationtime="{{animationTime}}"
|
|
27
|
+
windowed="{{windowed}}"
|
|
27
28
|
change:isdestroy="{{computed.updateValue(instanceId, 'isDestroy')}}"
|
|
28
|
-
change:options="{{computed.updateValue(instanceId, 'options')}}"
|
|
29
|
-
change:valuekey="{{computed.updateValue(instanceId,'valueKey')}}"
|
|
30
|
-
change:itemheight="{{computed.updateValue(instanceId,'itemHeight')}}"
|
|
31
|
-
change:visibleitemcount="{{computed.updateValue(instanceId, 'visibleItemCount')}}"
|
|
32
|
-
change:activeindex="{{computed.updateValue(instanceId,'activeIndex')}}"
|
|
33
|
-
change:loop="{{computed.updateValue(instanceId,'loop')}}"
|
|
34
|
-
change:animationtime="{{computed.updateValue(instanceId,'animationTime')}}"
|
|
35
|
-
change:
|
|
29
|
+
change:options="{{computed.updateValue(instanceId, 'options')}}"
|
|
30
|
+
change:valuekey="{{computed.updateValue(instanceId,'valueKey')}}"
|
|
31
|
+
change:itemheight="{{computed.updateValue(instanceId,'itemHeight')}}"
|
|
32
|
+
change:visibleitemcount="{{computed.updateValue(instanceId, 'visibleItemCount')}}"
|
|
33
|
+
change:activeindex="{{computed.updateValue(instanceId,'activeIndex')}}"
|
|
34
|
+
change:loop="{{computed.updateValue(instanceId,'loop')}}"
|
|
35
|
+
change:animationtime="{{computed.updateValue(instanceId,'animationTime')}}"
|
|
36
|
+
change:windowed="{{computed.updateValue(instanceId,'windowed')}}"
|
|
37
|
+
change:instanceid="{{computed.updateValue(instanceId,'instanceId')}}"
|
|
36
38
|
bind:touchstart="{{computed.touchStart(instanceId)}}"
|
|
37
39
|
catch:touchmove="{{computed.touchMove(instanceId)}}"
|
|
38
40
|
bind:touchend="{{computed.touchEnd(instanceId)}}"
|
|
@@ -226,7 +226,8 @@ function getDomState(ownerInstance) {
|
|
|
226
226
|
|
|
227
227
|
function initDomState(instanceId, ownerInstance) {
|
|
228
228
|
var state = getDomState(ownerInstance);
|
|
229
|
-
|
|
229
|
+
// options 不再通过 data-options 传入(避免把全量数组塞进 dataset / getDataset),
|
|
230
|
+
// 由 change:options -> updateValue('options', ...) 单独维护 state.options
|
|
230
231
|
updateState(instanceId, 'valueKey', state.valuekey)
|
|
231
232
|
updateState(instanceId, 'itemHeight', state.itemheight)
|
|
232
233
|
updateState(instanceId, 'visibleItemCount', state.visibleitemcount)
|
|
@@ -234,6 +235,7 @@ function initDomState(instanceId, ownerInstance) {
|
|
|
234
235
|
updateState(instanceId, 'loop', state.loop)
|
|
235
236
|
updateState(instanceId, 'animationTime', state.animationtime)
|
|
236
237
|
updateState(instanceId, 'changeAnimation', state.changeanimation)
|
|
238
|
+
updateState(instanceId, 'windowed', state.windowed)
|
|
237
239
|
}
|
|
238
240
|
|
|
239
241
|
|
|
@@ -262,10 +264,14 @@ function updateListView(instanceId, ownerInstance) {
|
|
|
262
264
|
var animationIndex = state.animationIndex || 0;
|
|
263
265
|
var newAnimationIndex = getNewAnimationIndex(animationIndex, activeIndex, length, state.loop);
|
|
264
266
|
var isSame = newAnimationIndex === animationIndex;
|
|
267
|
+
// 窗口化列的「停稳回中/重定位」会产生大幅度的 activeIndex 跳变,此时强制关闭过渡:
|
|
268
|
+
// 因为切片内容与位置是同步平移的,直接吸附即像素级一致,避免整轮旋转动画造成抖动。
|
|
269
|
+
var vOptionLength = state.visibleItemCount * 4 - 2;
|
|
270
|
+
var isWindowReposition = state.windowed && Math.abs(newAnimationIndex - animationIndex) > vOptionLength;
|
|
265
271
|
updateState(instanceId, 'animationIndex', newAnimationIndex)
|
|
266
272
|
updateVisibleOptions(state.animationIndex, instanceId);
|
|
267
273
|
updateWrapperStyle(instanceId, ownerInstance, {
|
|
268
|
-
transition: (!state.changeAnimation || isSame) ? 'none' : 'transform ' + state.animationTime + 'ms cubic-bezier(0.2, 0.9, 0.25, 1)'
|
|
274
|
+
transition: (!state.changeAnimation || isSame || isWindowReposition) ? 'none' : 'transform ' + state.animationTime + 'ms cubic-bezier(0.2, 0.9, 0.25, 1)'
|
|
269
275
|
});
|
|
270
276
|
}
|
|
271
277
|
|
|
@@ -373,7 +379,8 @@ function touchEnd(instanceId) {
|
|
|
373
379
|
|
|
374
380
|
// 惯性滚动参数配置
|
|
375
381
|
var minVelocity = 0.1; // 最小速度阈值,低于此值停止滚动
|
|
376
|
-
|
|
382
|
+
// 最大惯性滚动距离:下限 6 项,上限 100 项,避免超长列表单次快滑滚动上万项
|
|
383
|
+
var maxInertiaDistance = state.itemHeight * Math.min(Math.max(Math.floor(state.options.length/4), 6), 100);
|
|
377
384
|
// 计算惯性滚动距离
|
|
378
385
|
var inertiaDistance = 0;
|
|
379
386
|
if (recentVelocity > minVelocity) {
|
package/lib/picker/index.js
CHANGED
|
@@ -13,6 +13,11 @@ var __assign = (this && this.__assign) || function () {
|
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
var component_1 = require("../common/component");
|
|
15
15
|
var shared_1 = require("./shared");
|
|
16
|
+
// 超过该数量且非 loop 的列启用「窗口化」:只把当前位置附近的一段数据交给子 picker-column,
|
|
17
|
+
// 其余保存在逻辑层实例属性中,滚动停稳后再回中,避免把超长数组整份 setData 到渲染层 / 子组件。
|
|
18
|
+
var WINDOW_THRESHOLD = 2000;
|
|
19
|
+
var WINDOW_SIZE = 1000;
|
|
20
|
+
var clamp = function (value, min, max) { return Math.min(Math.max(value, min), max); };
|
|
16
21
|
(0, component_1.SmartComponent)({
|
|
17
22
|
classes: ['hairline-class', 'active-class', 'toolbar-class', 'column-class'],
|
|
18
23
|
props: __assign(__assign({}, shared_1.pickerProps), { valueKey: {
|
|
@@ -29,6 +34,12 @@ var shared_1 = require("./shared");
|
|
|
29
34
|
value: 0,
|
|
30
35
|
}, activeIndex: {
|
|
31
36
|
type: null,
|
|
37
|
+
observer: function () {
|
|
38
|
+
// 受控模式下 activeIndex(全局)变化时,若存在窗口化列需以新位置重建窗口
|
|
39
|
+
if (this._hasWindow) {
|
|
40
|
+
this.buildRenderColumns();
|
|
41
|
+
}
|
|
42
|
+
},
|
|
32
43
|
}, unit: {
|
|
33
44
|
type: String,
|
|
34
45
|
value: '',
|
|
@@ -38,6 +49,7 @@ var shared_1 = require("./shared");
|
|
|
38
49
|
observer: function (columns) {
|
|
39
50
|
if (columns === void 0) { columns = []; }
|
|
40
51
|
this.simple = columns.length && !columns[0].values;
|
|
52
|
+
this.buildRenderColumns();
|
|
41
53
|
if (Array.isArray(this.children) && this.children.length) {
|
|
42
54
|
this.setColumns().catch(function () { });
|
|
43
55
|
}
|
|
@@ -45,9 +57,15 @@ var shared_1 = require("./shared");
|
|
|
45
57
|
} }),
|
|
46
58
|
data: {
|
|
47
59
|
animating: false,
|
|
60
|
+
// 交给渲染层 / 子组件的列数据(窗口化列在此仅为切片,非窗口化列为完整数据)
|
|
61
|
+
renderColumns: [],
|
|
48
62
|
},
|
|
49
63
|
beforeCreate: function () {
|
|
50
64
|
var _this = this;
|
|
65
|
+
// 每列的窗口状态:{ windowed, start, size, full },保存在逻辑层,不进入 data
|
|
66
|
+
this._windows = [];
|
|
67
|
+
this._hasWindow = false;
|
|
68
|
+
this._repositioning = false;
|
|
51
69
|
Object.defineProperty(this, 'children', {
|
|
52
70
|
get: function () { return _this.selectAllComponents('.smart-picker__column') || []; },
|
|
53
71
|
});
|
|
@@ -59,11 +77,105 @@ var shared_1 = require("./shared");
|
|
|
59
77
|
},
|
|
60
78
|
methods: {
|
|
61
79
|
noop: function () { },
|
|
80
|
+
// 归一化列数据:simple 模式(columns 为一维数组)包一层
|
|
81
|
+
normalizedColumns: function () {
|
|
82
|
+
var columns = this.data.columns;
|
|
83
|
+
if (this.simple) {
|
|
84
|
+
return [{ values: columns }];
|
|
85
|
+
}
|
|
86
|
+
return Array.isArray(columns) ? columns : [];
|
|
87
|
+
},
|
|
88
|
+
// 解析某列的初始全局下标(优先 activeIndex,其次 defaultIndex)
|
|
89
|
+
resolveInitialIndex: function (column) {
|
|
90
|
+
var _a = this.data, defaultIndex = _a.defaultIndex, activeIndex = _a.activeIndex;
|
|
91
|
+
var columnActive = column.activeIndex === null || column.activeIndex === undefined
|
|
92
|
+
? activeIndex
|
|
93
|
+
: column.activeIndex;
|
|
94
|
+
var index;
|
|
95
|
+
if (columnActive !== null && columnActive !== undefined) {
|
|
96
|
+
index = columnActive;
|
|
97
|
+
}
|
|
98
|
+
else if (column.defaultIndex !== undefined) {
|
|
99
|
+
index = column.defaultIndex;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
index = defaultIndex;
|
|
103
|
+
}
|
|
104
|
+
var length = (column.values || []).length;
|
|
105
|
+
return clamp(index || 0, 0, Math.max(0, length - 1));
|
|
106
|
+
},
|
|
107
|
+
// 判断某列是否需要窗口化
|
|
108
|
+
shouldWindow: function (column) {
|
|
109
|
+
return (!this.data.loop &&
|
|
110
|
+
!column.loop &&
|
|
111
|
+
Array.isArray(column.values) &&
|
|
112
|
+
column.values.length > WINDOW_THRESHOLD);
|
|
113
|
+
},
|
|
114
|
+
// 基于当前 columns 计算 renderColumns 与每列窗口状态
|
|
115
|
+
buildRenderColumns: function () {
|
|
116
|
+
var _this = this;
|
|
117
|
+
var columns = this.normalizedColumns();
|
|
118
|
+
var windows = [];
|
|
119
|
+
var hasWindow = false;
|
|
120
|
+
var renderColumns = columns.map(function (column) {
|
|
121
|
+
var values = column.values || [];
|
|
122
|
+
if (!_this.shouldWindow(column)) {
|
|
123
|
+
windows.push({ windowed: false, start: 0, size: values.length, full: values });
|
|
124
|
+
return column;
|
|
125
|
+
}
|
|
126
|
+
hasWindow = true;
|
|
127
|
+
var globalIndex = _this.resolveInitialIndex(column);
|
|
128
|
+
var size = WINDOW_SIZE;
|
|
129
|
+
var start = clamp(globalIndex - Math.floor(size / 2), 0, Math.max(0, values.length - size));
|
|
130
|
+
var localIndex = globalIndex - start;
|
|
131
|
+
windows.push({ windowed: true, start: start, size: size, full: values });
|
|
132
|
+
return __assign(__assign({}, column), { values: values.slice(start, start + size), defaultIndex: localIndex, activeIndex: localIndex, _windowed: true });
|
|
133
|
+
});
|
|
134
|
+
this._windows = windows;
|
|
135
|
+
this._hasWindow = hasWindow;
|
|
136
|
+
this.setData({ renderColumns: renderColumns });
|
|
137
|
+
},
|
|
138
|
+
// 将窗口化列停稳后回中:使当前项回到窗口中心,纯坐标平移,静止态无感
|
|
139
|
+
recenterColumn: function (columnIndex) {
|
|
140
|
+
var _a;
|
|
141
|
+
var _this = this;
|
|
142
|
+
var win = this._windows[columnIndex];
|
|
143
|
+
if (!win || !win.windowed || this._repositioning)
|
|
144
|
+
return;
|
|
145
|
+
var column = this.getColumn(columnIndex);
|
|
146
|
+
if (!column)
|
|
147
|
+
return;
|
|
148
|
+
var localIndex = column.data.currentIndex;
|
|
149
|
+
var globalIndex = win.start + localIndex;
|
|
150
|
+
var size = win.size;
|
|
151
|
+
var newStart = clamp(globalIndex - Math.floor(size / 2), 0, Math.max(0, win.full.length - size));
|
|
152
|
+
if (newStart === win.start)
|
|
153
|
+
return;
|
|
154
|
+
this._repositioning = true;
|
|
155
|
+
win.start = newStart;
|
|
156
|
+
var newLocal = globalIndex - newStart;
|
|
157
|
+
// 同一次 setData 内同时更新切片与 active-index(局部),子组件原子接收,
|
|
158
|
+
// 配合 wxs 对大跳变强制 transition:none,实现无动画的静默回中
|
|
159
|
+
this.setData((_a = {},
|
|
160
|
+
_a["renderColumns[".concat(columnIndex, "].values")] = win.full.slice(newStart, newStart + size),
|
|
161
|
+
_a["renderColumns[".concat(columnIndex, "].activeIndex")] = newLocal,
|
|
162
|
+
_a), function () {
|
|
163
|
+
_this._repositioning = false;
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
// 局部下标 -> 全局下标
|
|
167
|
+
toGlobalIndex: function (columnIndex, localIndex) {
|
|
168
|
+
var win = this._windows[columnIndex];
|
|
169
|
+
return win && win.windowed ? win.start + localIndex : localIndex;
|
|
170
|
+
},
|
|
62
171
|
setColumns: function () {
|
|
63
172
|
var _this = this;
|
|
64
|
-
var
|
|
65
|
-
var columns = this.simple ? [{ values: data.columns }] : data.columns;
|
|
173
|
+
var columns = this.normalizedColumns();
|
|
66
174
|
var stack = columns.map(function (column, index) {
|
|
175
|
+
// 窗口化列由 renderColumns(wxml)响应式驱动,不走命令式全量下发
|
|
176
|
+
if (_this._windows[index] && _this._windows[index].windowed) {
|
|
177
|
+
return Promise.resolve();
|
|
178
|
+
}
|
|
67
179
|
return _this.setColumnValues(index, column.values);
|
|
68
180
|
});
|
|
69
181
|
return Promise.all(stack);
|
|
@@ -84,6 +196,7 @@ var shared_1 = require("./shared");
|
|
|
84
196
|
}
|
|
85
197
|
},
|
|
86
198
|
onChange: function (event) {
|
|
199
|
+
var columnIndex = this.simple ? 0 : event.currentTarget.dataset.index;
|
|
87
200
|
if (this.simple) {
|
|
88
201
|
this.$emit('change', {
|
|
89
202
|
picker: this,
|
|
@@ -98,6 +211,8 @@ var shared_1 = require("./shared");
|
|
|
98
211
|
index: event.currentTarget.dataset.index,
|
|
99
212
|
});
|
|
100
213
|
}
|
|
214
|
+
// 停稳后再回中,保证切换发生在静止态
|
|
215
|
+
this.recenterColumn(columnIndex);
|
|
101
216
|
},
|
|
102
217
|
// get column instance by index
|
|
103
218
|
getColumn: function (index) {
|
|
@@ -110,6 +225,19 @@ var shared_1 = require("./shared");
|
|
|
110
225
|
},
|
|
111
226
|
// set column value by index
|
|
112
227
|
setColumnValue: function (index, value) {
|
|
228
|
+
var win = this._windows[index];
|
|
229
|
+
if (win && win.windowed) {
|
|
230
|
+
// 窗口化列:在完整数据中按 valueKey 查找全局下标后跳转
|
|
231
|
+
var valueKey = this.data.valueKey;
|
|
232
|
+
for (var i = 0; i < win.full.length; i++) {
|
|
233
|
+
var option = win.full[i];
|
|
234
|
+
var text = option && typeof option === 'object' && valueKey in option ? option[valueKey] : option;
|
|
235
|
+
if (text === value) {
|
|
236
|
+
return this.setColumnIndex(index, i);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return Promise.resolve();
|
|
240
|
+
}
|
|
113
241
|
var column = this.getColumn(index);
|
|
114
242
|
if (column == null) {
|
|
115
243
|
return Promise.reject(new Error('setColumnValue: The corresponding column does not exist'));
|
|
@@ -118,10 +246,29 @@ var shared_1 = require("./shared");
|
|
|
118
246
|
},
|
|
119
247
|
// get column option index by column index
|
|
120
248
|
getColumnIndex: function (columnIndex) {
|
|
121
|
-
|
|
249
|
+
var _a, _b;
|
|
250
|
+
var local = (_b = (_a = (this.getColumn(columnIndex) || {}).data) === null || _a === void 0 ? void 0 : _a.currentIndex) !== null && _b !== void 0 ? _b : 0;
|
|
251
|
+
return this.toGlobalIndex(columnIndex, local);
|
|
122
252
|
},
|
|
123
253
|
// set column option index by column index
|
|
124
254
|
setColumnIndex: function (columnIndex, optionIndex) {
|
|
255
|
+
var _a;
|
|
256
|
+
var _this = this;
|
|
257
|
+
var win = this._windows[columnIndex];
|
|
258
|
+
if (win && win.windowed) {
|
|
259
|
+
// 以目标全局下标为中心重建窗口,子组件通过 active-index 定位
|
|
260
|
+
var size = win.size;
|
|
261
|
+
var globalIndex = clamp(optionIndex, 0, Math.max(0, win.full.length - 1));
|
|
262
|
+
var newStart = clamp(globalIndex - Math.floor(size / 2), 0, Math.max(0, win.full.length - size));
|
|
263
|
+
win.start = newStart;
|
|
264
|
+
this._repositioning = true;
|
|
265
|
+
return this.set((_a = {},
|
|
266
|
+
_a["renderColumns[".concat(columnIndex, "].values")] = win.full.slice(newStart, newStart + size),
|
|
267
|
+
_a["renderColumns[".concat(columnIndex, "].activeIndex")] = globalIndex - newStart,
|
|
268
|
+
_a)).then(function () {
|
|
269
|
+
_this._repositioning = false;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
125
272
|
var column = this.getColumn(columnIndex);
|
|
126
273
|
if (column == null) {
|
|
127
274
|
return Promise.reject(new Error('setColumnIndex: The corresponding column does not exist'));
|
|
@@ -130,16 +277,40 @@ var shared_1 = require("./shared");
|
|
|
130
277
|
},
|
|
131
278
|
// get options of column by index
|
|
132
279
|
getColumnValues: function (index) {
|
|
280
|
+
var win = this._windows[index];
|
|
281
|
+
if (win && win.windowed) {
|
|
282
|
+
return win.full;
|
|
283
|
+
}
|
|
133
284
|
return (this.children[index] || {}).data.options;
|
|
134
285
|
},
|
|
135
286
|
// set options of column by index
|
|
136
287
|
setColumnValues: function (index, options, needReset) {
|
|
288
|
+
var _a;
|
|
289
|
+
var _this = this;
|
|
137
290
|
if (needReset === void 0) { needReset = true; }
|
|
291
|
+
var win = this._windows[index];
|
|
292
|
+
if (win && win.windowed) {
|
|
293
|
+
// 替换完整数据并以头部为中心重建窗口
|
|
294
|
+
win.full = options || [];
|
|
295
|
+
var size = win.size;
|
|
296
|
+
var start = needReset ? 0 : clamp(win.start, 0, Math.max(0, win.full.length - size));
|
|
297
|
+
win.start = start;
|
|
298
|
+
this._repositioning = true;
|
|
299
|
+
return this.set((_a = {},
|
|
300
|
+
_a["renderColumns[".concat(index, "].values")] = win.full.slice(start, start + size),
|
|
301
|
+
_a["renderColumns[".concat(index, "].activeIndex")] = 0,
|
|
302
|
+
_a)).then(function () {
|
|
303
|
+
_this._repositioning = false;
|
|
304
|
+
});
|
|
305
|
+
}
|
|
138
306
|
var column = this.children[index];
|
|
139
307
|
if (column == null) {
|
|
140
308
|
return Promise.reject(new Error('setColumnValues: The corresponding column does not exist'));
|
|
141
309
|
}
|
|
142
|
-
var
|
|
310
|
+
var prevOptions = column.data.options || [];
|
|
311
|
+
// 先比长度(O(1))短路:长度不同必然不同,避免对超长数组做两次 JSON.stringify
|
|
312
|
+
var isSame = prevOptions.length === options.length &&
|
|
313
|
+
JSON.stringify(prevOptions) === JSON.stringify(options);
|
|
143
314
|
if (isSame) {
|
|
144
315
|
return Promise.resolve();
|
|
145
316
|
}
|
|
@@ -161,7 +332,10 @@ var shared_1 = require("./shared");
|
|
|
161
332
|
},
|
|
162
333
|
// get indexes of all columns
|
|
163
334
|
getIndexes: function () {
|
|
164
|
-
|
|
335
|
+
var _this = this;
|
|
336
|
+
return this.children.map(function (child, index) {
|
|
337
|
+
return _this.toGlobalIndex(index, child.data.currentIndex);
|
|
338
|
+
});
|
|
165
339
|
},
|
|
166
340
|
// set indexes of all columns
|
|
167
341
|
setIndexes: function (indexes) {
|
package/lib/picker/index.wxml
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
>
|
|
15
15
|
<picker-column
|
|
16
16
|
class="smart-picker__column"
|
|
17
|
-
wx:for="{{
|
|
17
|
+
wx:for="{{ renderColumns }}"
|
|
18
18
|
wx:key="index"
|
|
19
19
|
data-index="{{ index }}"
|
|
20
20
|
animation-time="{{ animationTime }}"
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
active-style="{{ activeStyle }}"
|
|
25
25
|
font-style="{{fontStyle ? fontStyle + ';' + computed.style(item.fontStyle): computed.style(item.fontStyle)}}"
|
|
26
26
|
options="{{ item.values }}"
|
|
27
|
+
windowed="{{ item._windowed || false }}"
|
|
27
28
|
disabled="{{ item.disabled || false }}"
|
|
28
29
|
unit="{{ item.unit || unit }}"
|
|
29
30
|
unit-gap="{{ item.unitGap }}"
|
|
@@ -21,6 +21,11 @@ var getId = function () {
|
|
|
21
21
|
className: String,
|
|
22
22
|
itemHeight: Number,
|
|
23
23
|
disabled: Boolean,
|
|
24
|
+
// 是否为父级窗口化列:为 true 时对大幅度的 active-index 跳变强制关闭过渡(用于停稳回中的静默重定位)
|
|
25
|
+
windowed: {
|
|
26
|
+
type: Boolean,
|
|
27
|
+
value: false,
|
|
28
|
+
},
|
|
24
29
|
visibleItemCount: Number,
|
|
25
30
|
activeStyle: {
|
|
26
31
|
type: String,
|
|
@@ -33,7 +38,12 @@ var getId = function () {
|
|
|
33
38
|
if (!this.data.isInit)
|
|
34
39
|
return;
|
|
35
40
|
this.updateUint(value);
|
|
36
|
-
|
|
41
|
+
// 窗口化列:切片与 activeIndex(局部)在同一批 setData 到达,
|
|
42
|
+
// 此处直接采用新的 activeIndex,使「新切片 + 新下标」原子落地,避免差一帧的错位闪烁
|
|
43
|
+
var nextIndex = this.data.windowed && this.data.activeIndex !== null && this.data.activeIndex !== undefined
|
|
44
|
+
? this.data.activeIndex
|
|
45
|
+
: this.data.currentIndex;
|
|
46
|
+
this.updateCurrentIndex(nextIndex);
|
|
37
47
|
this.updateVisibleOptions();
|
|
38
48
|
},
|
|
39
49
|
},
|
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
id="{{instanceId}}"
|
|
9
9
|
data-isdestroy="{{isDestroy}}"
|
|
10
10
|
data-changeanimation="{{changeAnimation}}"
|
|
11
|
-
data-options="{{options}}"
|
|
12
11
|
data-valuekey="{{valueKey}}"
|
|
13
12
|
data-itemheight="{{itemHeight}}"
|
|
14
13
|
data-visibleitemcount="{{visibleItemCount}}"
|
|
15
14
|
data-activeindex="{{currentIndex}}"
|
|
16
15
|
data-loop="{{loop && options.length > 1}}"
|
|
17
16
|
data-animationtime="{{animationTime}}"
|
|
17
|
+
data-windowed="{{windowed}}"
|
|
18
18
|
isdestroy="{{isDestroy}}"
|
|
19
|
-
options="{{options}}"
|
|
19
|
+
options="{{options}}"
|
|
20
20
|
activeindex="{{currentIndex}}"
|
|
21
21
|
changeanimation="{{changeAnimation}}"
|
|
22
22
|
loop="{{loop && options.length > 1}}"
|
|
@@ -24,15 +24,17 @@
|
|
|
24
24
|
itemheight="{{itemHeight}}"
|
|
25
25
|
visibleitemcount="{{visibleItemCount}}"
|
|
26
26
|
animationtime="{{animationTime}}"
|
|
27
|
+
windowed="{{windowed}}"
|
|
27
28
|
change:isdestroy="{{computed.updateValue(instanceId, 'isDestroy')}}"
|
|
28
|
-
change:options="{{computed.updateValue(instanceId, 'options')}}"
|
|
29
|
-
change:valuekey="{{computed.updateValue(instanceId,'valueKey')}}"
|
|
30
|
-
change:itemheight="{{computed.updateValue(instanceId,'itemHeight')}}"
|
|
31
|
-
change:visibleitemcount="{{computed.updateValue(instanceId, 'visibleItemCount')}}"
|
|
32
|
-
change:activeindex="{{computed.updateValue(instanceId,'activeIndex')}}"
|
|
33
|
-
change:loop="{{computed.updateValue(instanceId,'loop')}}"
|
|
34
|
-
change:animationtime="{{computed.updateValue(instanceId,'animationTime')}}"
|
|
35
|
-
change:
|
|
29
|
+
change:options="{{computed.updateValue(instanceId, 'options')}}"
|
|
30
|
+
change:valuekey="{{computed.updateValue(instanceId,'valueKey')}}"
|
|
31
|
+
change:itemheight="{{computed.updateValue(instanceId,'itemHeight')}}"
|
|
32
|
+
change:visibleitemcount="{{computed.updateValue(instanceId, 'visibleItemCount')}}"
|
|
33
|
+
change:activeindex="{{computed.updateValue(instanceId,'activeIndex')}}"
|
|
34
|
+
change:loop="{{computed.updateValue(instanceId,'loop')}}"
|
|
35
|
+
change:animationtime="{{computed.updateValue(instanceId,'animationTime')}}"
|
|
36
|
+
change:windowed="{{computed.updateValue(instanceId,'windowed')}}"
|
|
37
|
+
change:instanceid="{{computed.updateValue(instanceId,'instanceId')}}"
|
|
36
38
|
bind:touchstart="{{computed.touchStart(instanceId)}}"
|
|
37
39
|
catch:touchmove="{{computed.touchMove(instanceId)}}"
|
|
38
40
|
bind:touchend="{{computed.touchEnd(instanceId)}}"
|
|
@@ -226,7 +226,8 @@ function getDomState(ownerInstance) {
|
|
|
226
226
|
|
|
227
227
|
function initDomState(instanceId, ownerInstance) {
|
|
228
228
|
var state = getDomState(ownerInstance);
|
|
229
|
-
|
|
229
|
+
// options 不再通过 data-options 传入(避免把全量数组塞进 dataset / getDataset),
|
|
230
|
+
// 由 change:options -> updateValue('options', ...) 单独维护 state.options
|
|
230
231
|
updateState(instanceId, 'valueKey', state.valuekey)
|
|
231
232
|
updateState(instanceId, 'itemHeight', state.itemheight)
|
|
232
233
|
updateState(instanceId, 'visibleItemCount', state.visibleitemcount)
|
|
@@ -234,6 +235,7 @@ function initDomState(instanceId, ownerInstance) {
|
|
|
234
235
|
updateState(instanceId, 'loop', state.loop)
|
|
235
236
|
updateState(instanceId, 'animationTime', state.animationtime)
|
|
236
237
|
updateState(instanceId, 'changeAnimation', state.changeanimation)
|
|
238
|
+
updateState(instanceId, 'windowed', state.windowed)
|
|
237
239
|
}
|
|
238
240
|
|
|
239
241
|
|
|
@@ -262,10 +264,14 @@ function updateListView(instanceId, ownerInstance) {
|
|
|
262
264
|
var animationIndex = state.animationIndex || 0;
|
|
263
265
|
var newAnimationIndex = getNewAnimationIndex(animationIndex, activeIndex, length, state.loop);
|
|
264
266
|
var isSame = newAnimationIndex === animationIndex;
|
|
267
|
+
// 窗口化列的「停稳回中/重定位」会产生大幅度的 activeIndex 跳变,此时强制关闭过渡:
|
|
268
|
+
// 因为切片内容与位置是同步平移的,直接吸附即像素级一致,避免整轮旋转动画造成抖动。
|
|
269
|
+
var vOptionLength = state.visibleItemCount * 4 - 2;
|
|
270
|
+
var isWindowReposition = state.windowed && Math.abs(newAnimationIndex - animationIndex) > vOptionLength;
|
|
265
271
|
updateState(instanceId, 'animationIndex', newAnimationIndex)
|
|
266
272
|
updateVisibleOptions(state.animationIndex, instanceId);
|
|
267
273
|
updateWrapperStyle(instanceId, ownerInstance, {
|
|
268
|
-
transition: (!state.changeAnimation || isSame) ? 'none' : 'transform ' + state.animationTime + 'ms cubic-bezier(0.2, 0.9, 0.25, 1)'
|
|
274
|
+
transition: (!state.changeAnimation || isSame || isWindowReposition) ? 'none' : 'transform ' + state.animationTime + 'ms cubic-bezier(0.2, 0.9, 0.25, 1)'
|
|
269
275
|
});
|
|
270
276
|
}
|
|
271
277
|
|
|
@@ -373,7 +379,8 @@ function touchEnd(instanceId) {
|
|
|
373
379
|
|
|
374
380
|
// 惯性滚动参数配置
|
|
375
381
|
var minVelocity = 0.1; // 最小速度阈值,低于此值停止滚动
|
|
376
|
-
|
|
382
|
+
// 最大惯性滚动距离:下限 6 项,上限 100 项,避免超长列表单次快滑滚动上万项
|
|
383
|
+
var maxInertiaDistance = state.itemHeight * Math.min(Math.max(Math.floor(state.options.length/4), 6), 100);
|
|
377
384
|
// 计算惯性滚动距离
|
|
378
385
|
var inertiaDistance = 0;
|
|
379
386
|
if (recentVelocity > minVelocity) {
|