@ray-js/t-agent-ui-ray 0.2.3-beta-1 → 0.2.3-beta-3

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 (42) hide show
  1. package/dist/LazyScrollView/LazyItem/index.d.ts +0 -1
  2. package/dist/LazyScrollView/LazyItem/index.js +6 -12
  3. package/dist/LazyScrollView/LazyItem/index.tyml +7 -7
  4. package/dist/LazyScrollView/get-visible-items.sjs +53 -0
  5. package/dist/LazyScrollView/index.d.ts +1 -0
  6. package/dist/LazyScrollView/index.js +18 -1
  7. package/dist/LazyScrollView/index.tyml +2 -0
  8. package/dist/LazyScrollView/lazy-item-context.sjs +90 -0
  9. package/dist/LazyScrollView/lazy-scroll-view.sjs +20 -256
  10. package/dist/LazyScrollView/ordered-list.sjs +57 -0
  11. package/dist/LazyScrollView/scroll-view-context.sjs +237 -0
  12. package/dist/MessageActionBar/back.svg +1 -0
  13. package/dist/MessageActionBar/delete.svg +1 -0
  14. package/dist/MessageActionBar/index.d.ts +4 -0
  15. package/dist/MessageActionBar/index.js +79 -0
  16. package/dist/MessageActionBar/index.less +68 -0
  17. package/dist/MessageInput/MessageInputAIStream/index.js +4 -3
  18. package/dist/MessageInput/MessageInputAssistant/asr.d.ts +30 -0
  19. package/dist/MessageInput/MessageInputAssistant/asr.js +126 -0
  20. package/dist/MessageInput/MessageInputAssistant/index.js +5 -2
  21. package/dist/MessageInput/MessageInputAssistant/useAsrInput.js +1 -1
  22. package/dist/MessageList/PartRender.d.ts +15 -0
  23. package/dist/MessageList/PartRender.js +25 -0
  24. package/dist/MessageList/ScrollBottomView.d.ts +1 -0
  25. package/dist/MessageList/ScrollBottomView.js +4 -0
  26. package/dist/MessageList/index.d.ts +3 -5
  27. package/dist/MessageList/index.js +30 -47
  28. package/dist/MessageList/index.less +2 -22
  29. package/dist/MessageRender/index.d.ts +1 -2
  30. package/dist/MessageRender/index.js +0 -4
  31. package/dist/hooks/context.js +0 -1
  32. package/dist/hooks/useLongPress.js +1 -1
  33. package/dist/i18n/strings.d.ts +48 -16
  34. package/dist/i18n/strings.js +48 -16
  35. package/dist/index.d.ts +2 -1
  36. package/dist/index.js +2 -1
  37. package/dist/utils/abort.d.ts +38 -0
  38. package/dist/utils/abort.js +177 -0
  39. package/dist/utils/ttt.d.ts +98 -0
  40. package/dist/utils/ttt.js +54 -0
  41. package/package.json +2 -2
  42. package/dist/LazyScrollView/weak-ref.sjs +0 -45
@@ -5,7 +5,6 @@ interface Props {
5
5
  children?: React.ReactNode;
6
6
  update?: string;
7
7
  bindclick?: () => void;
8
- bindshow?: (event: { detail: { show: boolean } }) => void;
9
8
  style?: React.CSSProperties;
10
9
  ready?: boolean;
11
10
  scrollViewId?: string;
@@ -28,23 +28,17 @@ Component({
28
28
  type: Number
29
29
  }
30
30
  },
31
- data: {
32
- show: true
31
+ data: {},
32
+ relations: {
33
+ '../index': {
34
+ type: 'ancestor',
35
+ unlinked() {}
36
+ }
33
37
  },
34
38
  lifetimes: {},
35
39
  methods: {
36
40
  click() {
37
41
  this.triggerEvent('click');
38
- },
39
- setShow(show) {
40
- if (show !== this.data.show) {
41
- this.setData({
42
- show
43
- });
44
- this.triggerEvent('show', {
45
- show
46
- });
47
- }
48
42
  }
49
43
  },
50
44
  observers: {}
@@ -1,16 +1,16 @@
1
1
  <sjs src="../lazy-scroll-view.sjs" module="lazyScrollView"></sjs>
2
2
 
3
3
  <view
4
- change:update="{{lazyScrollView.itemUpdateObserver}}"
5
- update="{{update}}"
6
- change:ready="{{lazyScrollView.itemReadyObserver}}"
7
- ready="{{ready}}"
8
- change:index="{{lazyScrollView.itemIndexObserver}}"
9
- index="{{index}}"
4
+ change:data-update="{{lazyScrollView.itemUpdateObserver}}"
5
+ data-update="{{update}}"
6
+ change:data-ready="{{lazyScrollView.itemReadyObserver}}"
7
+ data-ready="{{ready}}"
8
+ change:data-index="{{lazyScrollView.itemIndexObserver}}"
9
+ data-index="{{index}}"
10
10
  bind:tap="click"
11
11
  data-scroll-view-id="{{scrollViewId}}"
12
12
  data-item-id="{{itemId}}"
13
13
  class="{{className || ''}}"
14
14
  >
15
- <slot ty:if="{{show}}"></slot>
15
+ <slot></slot>
16
16
  </view>
@@ -0,0 +1,53 @@
1
+ export function getVisibleItems(input) {
2
+ const { sizes, containerSize, offset, overscanCount } = input;
3
+
4
+ if (offset == null || !containerSize || !sizes.length) {
5
+ return null;
6
+ }
7
+
8
+ const total = sizes.length;
9
+
10
+ // Step 1: 构建 offsets(每个 item 的 top)
11
+ const offsets = [];
12
+ let currTop = 0;
13
+ for (let i = 0; i < total; i++) {
14
+ offsets.push(currTop);
15
+ currTop += sizes[i].height;
16
+ }
17
+
18
+ // Step 2: 二分查找第一个 bottom > visibleTop 的 item
19
+ const visibleTop = offset;
20
+ const visibleBottom = offset + containerSize;
21
+
22
+ let start = 0;
23
+ let end = total - 1;
24
+ let startIndex = total;
25
+ while (start <= end) {
26
+ const mid = Math.floor((start + end) / 2);
27
+ const bottom = offsets[mid] + sizes[mid].height;
28
+ if (bottom >= visibleTop) {
29
+ startIndex = mid;
30
+ end = mid - 1;
31
+ } else {
32
+ start = mid + 1;
33
+ }
34
+ }
35
+
36
+ // Step 3: 向后遍历直到 top > visibleBottom
37
+ let endIndex = startIndex;
38
+ while (endIndex < total && offsets[endIndex] < visibleBottom) {
39
+ endIndex++;
40
+ }
41
+
42
+ // Step 4: 往前数五个,往后数五个
43
+ const finalStartIndex = Math.max(0, startIndex - overscanCount);
44
+ const finalEndIndex = Math.min(total, endIndex + overscanCount);
45
+
46
+ // Step 5: 构建结果
47
+ const map = Object.create(null);
48
+ for (let i = finalStartIndex; i < finalEndIndex; i++) {
49
+ map[sizes[i].itemId] = true;
50
+ }
51
+
52
+ return map;
53
+ }
@@ -19,6 +19,7 @@ interface Props {
19
19
  scrollIntoView?: string;
20
20
  bindscroll?: (event: { detail: ScrollEventDetail }) => void;
21
21
  binddone?: (event: { detail: { scrollIntoView: string } }) => void;
22
+ bindchanged?: (event: { detail: { changes: Record<string, boolean> } }) => void;
22
23
  }
23
24
 
24
25
  declare function LazyScrollView(props: Props): React.JSX.Element;
@@ -1,3 +1,4 @@
1
+ import "core-js/modules/es.json.stringify.js";
1
2
  // eslint-disable-next-line no-undef
2
3
  Component({
3
4
  options: {
@@ -20,7 +21,23 @@ Component({
20
21
  value: ''
21
22
  }
22
23
  },
23
- data: {},
24
+ relations: {
25
+ './LazyItem/index': {
26
+ type: 'descendant',
27
+ unlinked(target) {
28
+ this.setData({
29
+ op: JSON.stringify({
30
+ type: 'detachItem',
31
+ itemId: target.properties.itemId,
32
+ t: Math.random()
33
+ })
34
+ });
35
+ }
36
+ }
37
+ },
38
+ data: {
39
+ op: ''
40
+ },
24
41
  lifetimes: {},
25
42
  methods: {},
26
43
  observers: {}
@@ -10,6 +10,8 @@
10
10
  change:scrollIntoView="{{lazyScrollView.scrollIntoViewObserver}}"
11
11
  scrollIntoView="{{scrollIntoView}}"
12
12
  bind:scroll="{{lazyScrollView.scroll}}"
13
+ change:data-op="{{lazyScrollView.opObserver}}"
14
+ data-op="{{op}}"
13
15
  >
14
16
  <slot></slot>
15
17
  </scroll-view>
@@ -0,0 +1,90 @@
1
+ // 为了尽可能防止内存泄漏,LazyItemContext 不引用 ownerInstance、instance
2
+ export class LazyItemContext {
3
+ constructor(prefix) {
4
+ this.prefix = prefix;
5
+ this.show = null; // 是否显示, null 表示未初始化
6
+ this.ready = false;
7
+ }
8
+
9
+ emitToScroll(ownerInstance, data) {
10
+ ownerInstance.eventChannel.emit(`${this.prefix}${this.scrollViewId}`, data);
11
+ }
12
+
13
+ init(ownerInstance, instance) {
14
+ const { scrollViewId, itemId, index } = instance.getDataset();
15
+ const rect = instance.getBoundingClientRect();
16
+ this.scrollViewId = scrollViewId;
17
+ this.itemId = itemId;
18
+ this.index = index;
19
+
20
+ this.height = rect.height;
21
+ this.eventName = `${this.prefix}${scrollViewId}/${itemId}`;
22
+
23
+ ownerInstance.eventChannel.on(this.eventName, (event) => {
24
+ switch (event.type) {
25
+ case 'refreshSize':
26
+ this._refreshSize(ownerInstance, instance, false);
27
+ ownerInstance.eventChannel.emit(`${this.eventName}/${event.id}`, this.height);
28
+ break;
29
+ }
30
+ });
31
+
32
+ this.emitToScroll(ownerInstance, {
33
+ type: 'itemAdd',
34
+ itemContext: this,
35
+ });
36
+ }
37
+
38
+ refreshSize(ownerInstance) {
39
+ return new Promise((resolve) => {
40
+ const id = Math.random()
41
+ ownerInstance.eventChannel.once(`${this.eventName}/${id}`, resolve)
42
+ ownerInstance.eventChannel.emit(this.eventName, { type: 'refreshSize', id });
43
+ })
44
+ }
45
+
46
+ _refreshSize(ownerInstance, instance, byUpdate = false) {
47
+ if (this.show === false) {
48
+ return;
49
+ }
50
+
51
+ if (byUpdate && this.ready && this.show === true) {
52
+ // 为了测量真实的高度,先清除 minHeight
53
+ instance.setStyle({
54
+ minHeight: '',
55
+ });
56
+ }
57
+
58
+ const rect = instance.getBoundingClientRect();
59
+ this.height = rect.height;
60
+ if (this.ready) {
61
+ instance.setStyle({
62
+ minHeight: `${rect.height}px`,
63
+ });
64
+ }
65
+ }
66
+
67
+ onUpdate(ownerInstance, instance) {
68
+ this._refreshSize(ownerInstance, instance, true);
69
+
70
+ if (this.ready) {
71
+ this.emitToScroll(ownerInstance,{
72
+ type: 'itemUpdate',
73
+ itemContext: this,
74
+ });
75
+ }
76
+ }
77
+
78
+ onReady(ownerInstance, instance) {
79
+ this.ready = true;
80
+ this._refreshSize(ownerInstance, instance, false);
81
+ }
82
+
83
+ onIndex(ownerInstance, instance, index) {
84
+ this.index = index;
85
+ this.emitToScroll(ownerInstance, {
86
+ type: 'itemIndexChange',
87
+ itemContext: this,
88
+ });
89
+ }
90
+ }
@@ -1,145 +1,18 @@
1
1
  // ownerInstance:表示的是触发事件的组件所在组件的 ComponentDescriptor 实例。如果触发事件的组件是在页面内的,ownerInstance 表示的是页面实例。
2
2
  // instance:表示触发事件的组件的 ComponentDescriptor 实例。
3
3
 
4
- const { createWeakRefPonyfill } = require('./weak-ref.sjs');
4
+ const { ScrollViewContext } = require('./scroll-view-context.sjs');
5
+ const { LazyItemContext } = require('./lazy-item-context.sjs');
5
6
 
6
- const WeakRef = createWeakRefPonyfill();
7
-
8
- // scrollViewId -> WeakRef(ScrollViewContext)
9
- const map = new Map();
10
-
11
- function getVisibleItemIdsBinary(input) {
12
- const {
13
- sizes,
14
- containerSize,
15
- offset,
16
- overscanCount,
17
- } = input
18
-
19
- if (!offset || !containerSize || !sizes.length) {
20
- return null
21
- }
22
-
23
- const total = sizes.length;
24
- const avgHeight = sizes.reduce((sum, s) => sum + s.height, 0) / total || 0;
25
- const overscanHeight = overscanCount * avgHeight;
26
-
27
- const visibleTop = offset;
28
- const visibleBottom = offset + containerSize;
29
- const overscanTop = visibleTop - overscanHeight;
30
- const overscanBottom = visibleBottom + overscanHeight;
31
-
32
- // Step 1: 构建 offsets(每个 item 的 top)
33
- const offsets = [];
34
- let currTop = 0;
35
- for (let i = 0; i < total; i++) {
36
- offsets.push(currTop);
37
- currTop += sizes[i].height;
38
- }
39
-
40
- // Step 2: 二分查找第一个 bottom > overscanTop 的 item
41
- let start = 0, end = total - 1, startIndex = total;
42
- while (start <= end) {
43
- const mid = Math.floor((start + end) / 2);
44
- const bottom = offsets[mid] + sizes[mid].height;
45
- if (bottom >= overscanTop) {
46
- startIndex = mid;
47
- end = mid - 1;
48
- } else {
49
- start = mid + 1;
50
- }
51
- }
52
-
53
- // Step 3: 向后遍历直到 top > overscanBottom
54
- const visibleMap = Object.create(null);
55
-
56
- let i = 0;
57
- while (i < startIndex) {
58
- i++
59
- }
60
-
61
- while (i < total && offsets[i] <= overscanBottom) {
62
- visibleMap[sizes[i].itemId] = true;
63
- i++;
64
- }
65
-
66
- return visibleMap
67
- }
7
+ const prefix = `t-agent-lazy-scroll-view-${Math.random()}@`;
68
8
 
69
9
  const getScrollViewContext = function (ownerInstance, instance) {
70
10
  const state = ownerInstance.getState()
71
11
  let context = state.scrollViewContext
72
12
  if (!context) {
73
- const { scrollViewId } = instance.getDataset()
74
- const rect = instance.getBoundingClientRect()
75
-
76
- context = {
77
- scrollViewId,
78
- height: rect.height,
79
- width: rect.width,
80
- overscanCount: 5,
81
- items: new Map(), // itemId -> WeakRef(ItemContext)
82
- updateShowPending: false,
83
- offset: 0,
84
- lastScrollEventDetail: null,
85
- lastScrollIntoView: null,
86
- refresh() {
87
- const rect = instance.getBoundingClientRect()
88
- context.height = rect.height
89
- context.width = rect.width
90
- },
91
- refreshSizes() {
92
- const sizes = [];
93
- context.items.forEach((ref) => {
94
- const itemContext = ref.deref()
95
- if (itemContext) {
96
- sizes.push(itemContext.data)
97
- }
98
- })
99
- sizes.sort((a, b) => a.index - b.index); // 按照 top 排序
100
- context.sizes = sizes;
101
- },
102
- updateVisibleItems() {
103
- if (context.updateShowPending) {
104
- return
105
- }
106
- context.updateShowPending = true;
107
- ownerInstance.requestAnimationFrame(() => {
108
- const visibleMap = getVisibleItemIdsBinary({
109
- sizes: context.sizes,
110
- containerSize: context.height,
111
- offset: context.offset,
112
- overscanCount: context.overscanCount,
113
- })
114
- if (visibleMap) {
115
- context.items.forEach((ref, itemId) => {
116
- const itemContext = ref.deref()
117
- if (itemContext) {
118
- itemContext.setShow(!!visibleMap[itemId]);
119
- }
120
- })
121
- }
122
- context.updateShowPending = false;
123
- })
124
- }
125
- }
13
+ context = new ScrollViewContext(prefix, 10);
14
+ context.init(ownerInstance, instance)
126
15
  state.scrollViewContext = context;
127
- // 使用 WeakRef 来避免内存泄漏
128
- map.set(scrollViewId, new WeakRef(context));
129
- }
130
- return context;
131
- }
132
-
133
- const getScrollViewContextById = function (scrollViewId) {
134
- const ref = map.get(scrollViewId);
135
- if (!ref) {
136
- return null;
137
- }
138
- const context = ref.deref();
139
- if (!context) {
140
- console.warn(`ScrollViewContext for scrollViewId ${scrollViewId} has been garbage collected.`);
141
- map.delete(scrollViewId);
142
- return null;
143
16
  }
144
17
  return context;
145
18
  }
@@ -147,89 +20,28 @@ const getScrollViewContextById = function (scrollViewId) {
147
20
  const scroll = function (event, ownerInstance) {
148
21
  const instance = event.instance
149
22
  const context = getScrollViewContext(ownerInstance, instance)
150
- context.offset = event.detail.scrollTop;
151
- // 触发父组件的滚动事件
152
- ownerInstance.triggerEvent('scroll', {
153
- ...event.detail,
154
- clientHeight: context.height,
155
- clientWidth: context.width,
156
- });
157
- context.lastScrollEventDetail = event.detail;
158
- context.updateVisibleItems();
23
+ context.onScroll(event, ownerInstance, instance)
159
24
  }
160
25
 
161
26
  const getItemContext = function (ownerInstance, instance) {
162
27
  const state = ownerInstance.getState()
163
28
  let itemContext = state.context
164
29
  if (!itemContext) {
165
- const { scrollViewId, itemId } = instance.getDataset()
166
- const rect = instance.getBoundingClientRect()
167
- itemContext = {
168
- show: true,
169
- scrollViewId,
170
- data: {
171
- itemId,
172
- index: null,
173
- height: rect.height,
174
- },
175
- ready: false,
176
- setShow(show) {
177
- if (show === itemContext.show || !itemContext.ready) {
178
- return;
179
- }
180
- itemContext.show = show;
181
- if (show) {
182
- ownerInstance.requestAnimationFrame(() => {
183
- itemContext.refreshSize();
184
- })
185
- }
186
- ownerInstance.callMethod('setShow', show);
187
- },
188
- refreshSize(byUpdate = false) {
189
- if (byUpdate && itemContext.ready && itemContext.show) {
190
- // 为了测量真实的高度,先清除 minHeight
191
- instance.setStyle({
192
- minHeight: '',
193
- });
194
- }
195
-
196
- const rect = instance.getBoundingClientRect()
197
- itemContext.data.height = rect.height;
198
- if (itemContext.ready) {
199
- instance.setStyle({
200
- minHeight: `${rect.height}px`,
201
- });
202
- }
203
- }
204
- }
205
- const context = getScrollViewContextById(scrollViewId)
206
- if (context) {
207
- context.items.set(itemId, new WeakRef(itemContext));
208
- } else {
209
- console.warn(`ScrollViewContext not found for scrollViewId: ${scrollViewId}`);
210
- }
30
+ itemContext = new LazyItemContext(prefix);
31
+ itemContext.init(ownerInstance, instance);
211
32
  state.context = itemContext;
212
33
  }
213
34
  return itemContext;
214
35
  }
215
36
 
216
37
  const itemUpdateObserver = function (newValue, oldValue, ownerInstance, instance) {
217
- if (newValue === oldValue) {
38
+ // 没有旧值就表示第一次挂载,跳过,以防止刚挂载上时,由于外部 show === false,导致高度计算为 0。
39
+ if (newValue === oldValue || !oldValue) {
218
40
  return;
219
41
  }
220
42
 
221
43
  const itemContext = getItemContext(ownerInstance, instance)
222
- itemContext.refreshSize(true);
223
- if (itemContext.ready) {
224
- ownerInstance.requestAnimationFrame(() => {
225
- const context = getScrollViewContextById(itemContext.scrollViewId);
226
- if (context) {
227
- context.refreshSizes();
228
- context.updateVisibleItems();
229
- }
230
- })
231
- }
232
-
44
+ itemContext.onUpdate(ownerInstance, instance);
233
45
  };
234
46
 
235
47
  const itemReadyObserver = function (newValue, oldValue, ownerInstance, instance) {
@@ -238,13 +50,7 @@ const itemReadyObserver = function (newValue, oldValue, ownerInstance, instance)
238
50
  }
239
51
 
240
52
  const itemContext = getItemContext(ownerInstance, instance)
241
-
242
- // ready 属性变化时,设置 ready 状态
243
- if (newValue === true) {
244
- itemContext.ready = true;
245
- }
246
-
247
- itemContext.refreshSize();
53
+ itemContext.onReady(ownerInstance, instance);
248
54
  };
249
55
 
250
56
  const itemIndexObserver = function (newValue, oldValue, ownerInstance, instance) {
@@ -252,60 +58,17 @@ const itemIndexObserver = function (newValue, oldValue, ownerInstance, instance)
252
58
  return;
253
59
  }
254
60
  const itemContext = getItemContext(ownerInstance, instance)
255
- itemContext.data.index = newValue;
256
- const context = getScrollViewContextById(itemContext.scrollViewId);
257
- if (context) {
258
- context.refreshSizes();
259
- context.updateVisibleItems();
260
- }
61
+ itemContext.onIndex(ownerInstance, instance, newValue);
62
+ }
63
+
64
+ const opObserver = function (opJson, old, ownerInstance, instance) {
65
+ const context = getScrollViewContext(ownerInstance, instance);
66
+ context.doOp(opJson, old, ownerInstance, instance);
261
67
  }
262
68
 
263
69
  const scrollIntoViewObserver = function (newValue, oldValue, ownerInstance, instance) {
264
70
  const context = getScrollViewContext(ownerInstance, instance)
265
- if (newValue === oldValue) {
266
- return;
267
- }
268
-
269
- ownerInstance.requestAnimationFrame(() => {
270
- ownerInstance.triggerEvent('done', {
271
- scrollIntoView: newValue,
272
- })
273
- })
274
-
275
- if (!newValue) {
276
- return
277
- }
278
-
279
- const last = context.lastScrollIntoView;
280
-
281
- if (last && last === newValue) {
282
- return;
283
- }
284
-
285
- context.lastScrollIntoView = newValue
286
-
287
- const rect = instance.getBoundingClientRect()
288
- if (context.lastScrollEventDetail == null) {
289
- return
290
- }
291
-
292
- // 这个就是用来滚动到底部的,所以直接计算底部 offset 就行
293
-
294
- const lastOffset = context.offset
295
- context.offset = context.lastScrollEventDetail.scrollHeight - rect.height;
296
- context.refresh()
297
- context.updateVisibleItems();
298
-
299
- ownerInstance.triggerEvent('scroll', {
300
- scrollTop: context.offset,
301
- deltaX: 0,
302
- deltaY: context.offset - lastOffset,
303
- scrollLeft: context.lastScrollEventDetail.scrollLeft,
304
- scrollHeight: context.lastScrollEventDetail.scrollHeight,
305
- scrollWidth: context.lastScrollEventDetail.scrollWidth,
306
- clientHeight: context.height,
307
- clientWidth: context.width,
308
- });
71
+ context.onScrollBottom(newValue, oldValue, ownerInstance, instance)
309
72
  }
310
73
 
311
74
  export default {
@@ -313,5 +76,6 @@ export default {
313
76
  itemUpdateObserver,
314
77
  itemReadyObserver,
315
78
  scrollIntoViewObserver,
79
+ opObserver,
316
80
  scroll
317
81
  };
@@ -0,0 +1,57 @@
1
+ export class OrderedList {
2
+ constructor() {
3
+ this.map = new Map(); // 用于快速通过 itemId 查找元素
4
+ this.list = []; // 存储有序元素
5
+ }
6
+
7
+ // 添加或更新元素
8
+ upsert(item) {
9
+ const { itemId, index } = item;
10
+ if (!itemId || typeof index !== 'number') {
11
+ throw new Error('Item must have itemId and index');
12
+ }
13
+
14
+ const exists = this.map.has(itemId);
15
+ if (exists) {
16
+ // 更新旧元素
17
+ const i = this.list.findIndex(el => el.itemId === itemId);
18
+ if (i !== -1) {
19
+ this.list.splice(i, 1);
20
+ }
21
+ }
22
+
23
+ this.map.set(itemId, item);
24
+ // 插入并排序(插入后保持 list 有序)
25
+ this._insertSorted(item);
26
+ }
27
+
28
+ // 删除元素
29
+ remove(itemId) {
30
+ if (this.map.has(itemId)) {
31
+ this.map.delete(itemId);
32
+ const i = this.list.findIndex(el => el.itemId === itemId);
33
+ if (i !== -1) {
34
+ this.list.splice(i, 1);
35
+ }
36
+ }
37
+ }
38
+
39
+ // 获取有序元素
40
+ getAll() {
41
+ return this.list;
42
+ }
43
+
44
+ _insertSorted(item) {
45
+ // 二分插入,保持排序
46
+ let left = 0, right = this.list.length;
47
+ while (left < right) {
48
+ const mid = (left + right) >> 1;
49
+ if (this.list[mid].index < item.index) {
50
+ left = mid + 1;
51
+ } else {
52
+ right = mid;
53
+ }
54
+ }
55
+ this.list.splice(left, 0, item);
56
+ }
57
+ }