selective-ui 1.3.1 → 1.4.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.
- package/dist/selective-ui.css +0 -6
- package/dist/selective-ui.css.map +1 -1
- package/dist/selective-ui.esm.js +271 -559
- package/dist/selective-ui.esm.js.map +1 -1
- package/dist/selective-ui.esm.min.js +2 -2
- package/dist/selective-ui.esm.min.js.br +0 -0
- package/dist/selective-ui.min.css +1 -1
- package/dist/selective-ui.min.css.br +0 -0
- package/dist/selective-ui.min.js +2 -2
- package/dist/selective-ui.min.js.br +0 -0
- package/dist/selective-ui.umd.js +273 -561
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +12 -12
- package/src/ts/adapter/mixed-adapter.ts +147 -68
- package/src/ts/components/accessorybox.ts +14 -11
- package/src/ts/components/directive.ts +1 -1
- package/src/ts/components/option-handle.ts +12 -9
- package/src/ts/components/placeholder.ts +5 -5
- package/src/ts/components/popup/empty-state.ts +5 -5
- package/src/ts/components/popup/loading-state.ts +5 -5
- package/src/ts/components/popup/popup.ts +138 -76
- package/src/ts/components/searchbox.ts +17 -13
- package/src/ts/components/selectbox.ts +258 -83
- package/src/ts/core/base/adapter.ts +39 -14
- package/src/ts/core/base/fenwick.ts +3 -2
- package/src/ts/core/base/lifecycle.ts +14 -4
- package/src/ts/core/base/model.ts +17 -15
- package/src/ts/core/base/recyclerview.ts +7 -5
- package/src/ts/core/base/view.ts +10 -5
- package/src/ts/core/base/virtual-recyclerview.ts +89 -37
- package/src/ts/core/model-manager.ts +48 -21
- package/src/ts/core/search-controller.ts +174 -56
- package/src/ts/global.ts +5 -8
- package/src/ts/index.ts +2 -2
- package/src/ts/models/group-model.ts +33 -8
- package/src/ts/models/option-model.ts +60 -19
- package/src/ts/services/dataset-observer.ts +6 -3
- package/src/ts/services/ea-observer.ts +1 -1
- package/src/ts/services/effector.ts +22 -12
- package/src/ts/services/refresher.ts +7 -3
- package/src/ts/services/resize-observer.ts +24 -11
- package/src/ts/services/select-observer.ts +2 -2
- package/src/ts/types/components/popup.type.ts +18 -1
- package/src/ts/types/components/searchbox.type.ts +43 -30
- package/src/ts/types/components/state.box.type.ts +1 -1
- package/src/ts/types/core/base/adapter.type.ts +13 -5
- package/src/ts/types/core/base/lifecycle.type.ts +1 -2
- package/src/ts/types/core/base/model.type.ts +3 -3
- package/src/ts/types/core/base/recyclerview.type.ts +7 -5
- package/src/ts/types/core/base/view.type.ts +6 -6
- package/src/ts/types/core/base/virtual-recyclerview.type.ts +45 -46
- package/src/ts/types/core/search-controller.type.ts +18 -2
- package/src/ts/types/css.d.ts +1 -0
- package/src/ts/types/plugins/plugin.type.ts +2 -2
- package/src/ts/types/services/effector.type.ts +25 -25
- package/src/ts/types/services/resize-observer.type.ts +23 -12
- package/src/ts/types/utils/callback-scheduler.type.ts +2 -2
- package/src/ts/types/utils/ievents.type.ts +1 -1
- package/src/ts/types/utils/istorage.type.ts +62 -60
- package/src/ts/types/utils/libs.type.ts +19 -17
- package/src/ts/types/utils/selective.type.ts +6 -3
- package/src/ts/types/views/view.group.type.ts +9 -5
- package/src/ts/types/views/view.option.type.ts +39 -17
- package/src/ts/utils/callback-scheduler.ts +12 -7
- package/src/ts/utils/ievents.ts +12 -5
- package/src/ts/utils/istorage.ts +5 -3
- package/src/ts/utils/libs.ts +122 -43
- package/src/ts/utils/selective.ts +15 -8
- package/src/ts/views/group-view.ts +11 -9
- package/src/ts/views/option-view.ts +37 -18
|
@@ -62,9 +62,12 @@ import { LifecycleState } from "src/ts/types/core/base/lifecycle.type";
|
|
|
62
62
|
* @see {@link ModelContract}
|
|
63
63
|
*/
|
|
64
64
|
export class Adapter<
|
|
65
|
-
TItem extends ModelContract<any, any> & { view
|
|
66
|
-
TViewer extends ViewContract<any
|
|
67
|
-
>
|
|
65
|
+
TItem extends ModelContract<any, any> & { view?: TViewer; isInit: boolean },
|
|
66
|
+
TViewer extends ViewContract<any>,
|
|
67
|
+
>
|
|
68
|
+
extends Lifecycle
|
|
69
|
+
implements AdapterContract<TItem>
|
|
70
|
+
{
|
|
68
71
|
/**
|
|
69
72
|
* Current list of items managed by the adapter.
|
|
70
73
|
*
|
|
@@ -118,7 +121,11 @@ export class Adapter<
|
|
|
118
121
|
* @param {number} position - Index of the item within the adapter item list.
|
|
119
122
|
* @returns {void}
|
|
120
123
|
*/
|
|
121
|
-
public onViewHolder(
|
|
124
|
+
public onViewHolder(
|
|
125
|
+
item: TItem,
|
|
126
|
+
viewer?: TViewer,
|
|
127
|
+
position?: number,
|
|
128
|
+
): void {
|
|
122
129
|
void position;
|
|
123
130
|
|
|
124
131
|
const v = viewer;
|
|
@@ -142,8 +149,15 @@ export class Adapter<
|
|
|
142
149
|
* @returns {void}
|
|
143
150
|
* @see {@link changingProp}
|
|
144
151
|
*/
|
|
145
|
-
public onPropChanging(
|
|
146
|
-
|
|
152
|
+
public onPropChanging(
|
|
153
|
+
propName: string,
|
|
154
|
+
callback: (...args: unknown[]) => void,
|
|
155
|
+
): void {
|
|
156
|
+
Libs.callbackScheduler.on(
|
|
157
|
+
`${propName}ing_${this.adapterKey}`,
|
|
158
|
+
callback,
|
|
159
|
+
{ debounce: 0 },
|
|
160
|
+
);
|
|
147
161
|
}
|
|
148
162
|
|
|
149
163
|
/**
|
|
@@ -159,8 +173,13 @@ export class Adapter<
|
|
|
159
173
|
* @returns {void}
|
|
160
174
|
* @see {@link changeProp}
|
|
161
175
|
*/
|
|
162
|
-
public onPropChanged(
|
|
163
|
-
|
|
176
|
+
public onPropChanged(
|
|
177
|
+
propName: string,
|
|
178
|
+
callback: (...args: unknown[]) => void,
|
|
179
|
+
): void {
|
|
180
|
+
Libs.callbackScheduler.on(`${propName}_${this.adapterKey}`, callback, {
|
|
181
|
+
debounce: 0,
|
|
182
|
+
});
|
|
164
183
|
}
|
|
165
184
|
|
|
166
185
|
/**
|
|
@@ -174,7 +193,10 @@ export class Adapter<
|
|
|
174
193
|
* @returns {Promise<void>} Resolves when scheduled callbacks complete.
|
|
175
194
|
*/
|
|
176
195
|
public changeProp(propName: string, ...params: unknown[]): Promise<void> {
|
|
177
|
-
return Libs.callbackScheduler.run(
|
|
196
|
+
return Libs.callbackScheduler.run(
|
|
197
|
+
`${propName}_${this.adapterKey}`,
|
|
198
|
+
...params,
|
|
199
|
+
);
|
|
178
200
|
}
|
|
179
201
|
|
|
180
202
|
/**
|
|
@@ -188,7 +210,10 @@ export class Adapter<
|
|
|
188
210
|
* @returns {Promise<void>} Resolves when scheduled callbacks complete.
|
|
189
211
|
*/
|
|
190
212
|
public changingProp(propName: string, ...params: unknown[]): Promise<void> {
|
|
191
|
-
return Libs.callbackScheduler.run(
|
|
213
|
+
return Libs.callbackScheduler.run(
|
|
214
|
+
`${propName}ing_${this.adapterKey}`,
|
|
215
|
+
...params,
|
|
216
|
+
);
|
|
192
217
|
}
|
|
193
218
|
|
|
194
219
|
/**
|
|
@@ -198,9 +223,9 @@ export class Adapter<
|
|
|
198
223
|
*
|
|
199
224
|
* @param {HTMLElement} parent - Container element that will host the viewer.
|
|
200
225
|
* @param {TItem} item - The model for which the viewer is created.
|
|
201
|
-
* @returns {TViewer
|
|
226
|
+
* @returns {TViewer} The created viewer instance; `null` by default.
|
|
202
227
|
*/
|
|
203
|
-
public viewHolder(parent: HTMLElement, item: TItem): TViewer
|
|
228
|
+
public viewHolder?(parent: HTMLElement, item: TItem): TViewer {
|
|
204
229
|
void parent;
|
|
205
230
|
void item;
|
|
206
231
|
return null;
|
|
@@ -317,9 +342,9 @@ export class Adapter<
|
|
|
317
342
|
}
|
|
318
343
|
|
|
319
344
|
this.recyclerView = null;
|
|
320
|
-
this.items.forEach(item => {
|
|
345
|
+
this.items.forEach((item) => {
|
|
321
346
|
item?.destroy?.();
|
|
322
347
|
});
|
|
323
348
|
this.items = [];
|
|
324
349
|
}
|
|
325
|
-
}
|
|
350
|
+
}
|
|
@@ -131,7 +131,8 @@ export class Fenwick extends Lifecycle {
|
|
|
131
131
|
* Returns 0 if the first element already exceeds `target`.
|
|
132
132
|
*/
|
|
133
133
|
public lowerBoundPrefix(target: number): number {
|
|
134
|
-
let idx = 0,
|
|
134
|
+
let idx = 0,
|
|
135
|
+
bitMask = 1;
|
|
135
136
|
while (bitMask << 1 <= this.stackNum) bitMask <<= 1;
|
|
136
137
|
|
|
137
138
|
let cur = 0;
|
|
@@ -144,4 +145,4 @@ export class Fenwick extends Lifecycle {
|
|
|
144
145
|
}
|
|
145
146
|
return idx;
|
|
146
147
|
}
|
|
147
|
-
}
|
|
148
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
LifecycleHookContext,
|
|
3
|
+
LifecycleHooks,
|
|
4
|
+
LifecycleState,
|
|
5
|
+
} from "src/ts/types/core/base/lifecycle.type";
|
|
2
6
|
|
|
3
7
|
type LifecycleHookName = keyof LifecycleHooks;
|
|
4
8
|
|
|
@@ -59,7 +63,10 @@ export class Lifecycle {
|
|
|
59
63
|
* This map is initialized with keys for all supported hooks in the constructor.
|
|
60
64
|
* Callbacks are cleared on {@link destroy}.
|
|
61
65
|
*/
|
|
62
|
-
private hooks: Map<
|
|
66
|
+
private hooks: Map<
|
|
67
|
+
LifecycleHookName,
|
|
68
|
+
Set<(ctx: LifecycleHookContext) => void>
|
|
69
|
+
> = new Map();
|
|
63
70
|
|
|
64
71
|
/**
|
|
65
72
|
* Constructs the lifecycle manager and pre-registers hook containers.
|
|
@@ -98,7 +105,10 @@ export class Lifecycle {
|
|
|
98
105
|
* @param {(ctx: LifecycleHookContext) => void} fn - Callback to remove.
|
|
99
106
|
* @returns {this} The current instance (chainable).
|
|
100
107
|
*/
|
|
101
|
-
off(
|
|
108
|
+
off(
|
|
109
|
+
hook: LifecycleHookName,
|
|
110
|
+
fn: (ctx: LifecycleHookContext) => void,
|
|
111
|
+
): this {
|
|
102
112
|
this.hooks.get(hook)!.delete(fn);
|
|
103
113
|
return this;
|
|
104
114
|
}
|
|
@@ -255,4 +265,4 @@ export class Lifecycle {
|
|
|
255
265
|
set.clear();
|
|
256
266
|
}
|
|
257
267
|
}
|
|
258
|
-
}
|
|
268
|
+
}
|
|
@@ -43,16 +43,18 @@ export class Model<
|
|
|
43
43
|
TTarget extends HTMLElement,
|
|
44
44
|
TTags extends Record<string, HTMLElement>,
|
|
45
45
|
TView extends ViewContract<TTags>,
|
|
46
|
-
TOptions = unknown
|
|
47
|
-
>
|
|
48
|
-
|
|
46
|
+
TOptions = unknown,
|
|
47
|
+
>
|
|
48
|
+
extends Lifecycle
|
|
49
|
+
implements ModelContract<TTarget, TView>
|
|
50
|
+
{
|
|
49
51
|
/**
|
|
50
52
|
* The currently bound target DOM element.
|
|
51
53
|
*
|
|
52
54
|
* This element typically represents the source-of-truth node in the host DOM (e.g., a native `<option>`).
|
|
53
55
|
* May be replaced via {@link updateTarget} during reconciliation.
|
|
54
56
|
*/
|
|
55
|
-
public targetElement
|
|
57
|
+
public targetElement?: TTarget;
|
|
56
58
|
|
|
57
59
|
/**
|
|
58
60
|
* Configuration options supplied at construction time.
|
|
@@ -66,25 +68,25 @@ export class Model<
|
|
|
66
68
|
* Ownership: this model will destroy the view on {@link destroy}.
|
|
67
69
|
* The view may be attached/assigned by external orchestrators (Adapter/RecyclerView) after construction.
|
|
68
70
|
*/
|
|
69
|
-
public view
|
|
71
|
+
public view?: TView;
|
|
70
72
|
|
|
71
73
|
/**
|
|
72
74
|
* Position index used by list infrastructure for ordering/tracking.
|
|
73
75
|
* Semantics are library-specific (e.g., top-level index or adapter position).
|
|
74
76
|
*/
|
|
75
|
-
public position = -1;
|
|
77
|
+
public position: number = -1;
|
|
76
78
|
|
|
77
79
|
/**
|
|
78
80
|
* Indicates whether this model has completed its initial binding step.
|
|
79
81
|
* Typically set by the adapter/view binding layer to prevent duplicate listener wiring.
|
|
80
82
|
*/
|
|
81
|
-
public isInit = false;
|
|
83
|
+
public isInit: boolean = false;
|
|
82
84
|
|
|
83
85
|
/**
|
|
84
86
|
* Indicates whether this model has been removed/destroyed from the active dataset.
|
|
85
87
|
* Set to `true` during {@link destroy}.
|
|
86
88
|
*/
|
|
87
|
-
public isRemoved = false;
|
|
89
|
+
public isRemoved: boolean = false;
|
|
88
90
|
|
|
89
91
|
/**
|
|
90
92
|
* Returns the current "value" associated with the bound target element.
|
|
@@ -107,13 +109,13 @@ export class Model<
|
|
|
107
109
|
* - Calls {@link Lifecycle.init} immediately (`NEW → INITIALIZED`).
|
|
108
110
|
*
|
|
109
111
|
* @param {TOptions} options - Configuration options for the model.
|
|
110
|
-
* @param {TTarget
|
|
111
|
-
* @param {TView
|
|
112
|
+
* @param {TTarget} [targetElement=null] - Optional DOM element to bind.
|
|
113
|
+
* @param {TView} [view=null] - Optional view responsible for rendering this model.
|
|
112
114
|
*/
|
|
113
115
|
public constructor(
|
|
114
116
|
options: TOptions,
|
|
115
|
-
targetElement
|
|
116
|
-
view
|
|
117
|
+
targetElement?: TTarget,
|
|
118
|
+
view?: TView,
|
|
117
119
|
) {
|
|
118
120
|
super();
|
|
119
121
|
this.options = options;
|
|
@@ -134,10 +136,10 @@ export class Model<
|
|
|
134
136
|
* - Assigns {@link targetElement}.
|
|
135
137
|
* - Calls {@link Lifecycle.update} (guarded by lifecycle state).
|
|
136
138
|
*
|
|
137
|
-
* @param {TTarget
|
|
139
|
+
* @param {TTarget} targetElement - The new DOM element to associate with this model.
|
|
138
140
|
* @returns {void}
|
|
139
141
|
*/
|
|
140
|
-
public updateTarget(targetElement
|
|
142
|
+
public updateTarget(targetElement?: TTarget): void {
|
|
141
143
|
this.targetElement = targetElement;
|
|
142
144
|
this.update();
|
|
143
145
|
}
|
|
@@ -167,4 +169,4 @@ export class Model<
|
|
|
167
169
|
|
|
168
170
|
super.destroy();
|
|
169
171
|
}
|
|
170
|
-
}
|
|
172
|
+
}
|
|
@@ -22,9 +22,11 @@ import { LifecycleState } from "src/ts/types/core/base/lifecycle.type";
|
|
|
22
22
|
*/
|
|
23
23
|
export class RecyclerView<
|
|
24
24
|
TItem extends ModelContract<any, any>,
|
|
25
|
-
TAdapter extends AdapterContract<TItem
|
|
26
|
-
>
|
|
27
|
-
|
|
25
|
+
TAdapter extends AdapterContract<TItem>,
|
|
26
|
+
>
|
|
27
|
+
extends Lifecycle
|
|
28
|
+
implements RecyclerViewContract<TAdapter>
|
|
29
|
+
{
|
|
28
30
|
/** Root container that hosts rendered item views. */
|
|
29
31
|
public viewElement: HTMLDivElement | null = null;
|
|
30
32
|
|
|
@@ -113,10 +115,10 @@ export class RecyclerView<
|
|
|
113
115
|
if (this.is(LifecycleState.DESTROYED)) {
|
|
114
116
|
return;
|
|
115
117
|
}
|
|
116
|
-
|
|
118
|
+
|
|
117
119
|
this.viewElement = null;
|
|
118
120
|
this.adapter = null;
|
|
119
121
|
|
|
120
122
|
super.destroy();
|
|
121
123
|
}
|
|
122
|
-
}
|
|
124
|
+
}
|
package/src/ts/core/base/view.ts
CHANGED
|
@@ -39,13 +39,16 @@ import { LifecycleState } from "src/ts/types/core/base/lifecycle.type";
|
|
|
39
39
|
* @see {@link ViewContract}
|
|
40
40
|
* @see {@link LifecycleState}
|
|
41
41
|
*/
|
|
42
|
-
export class View<TTags extends Record<string, HTMLElement>>
|
|
42
|
+
export class View<TTags extends Record<string, HTMLElement>>
|
|
43
|
+
extends Lifecycle
|
|
44
|
+
implements ViewContract<TTags>
|
|
45
|
+
{
|
|
43
46
|
/**
|
|
44
47
|
* Host container element into which this view's root element is rendered/attached.
|
|
45
48
|
*
|
|
46
49
|
* This reference is captured at construction time and cleared on {@link destroy}.
|
|
47
50
|
*/
|
|
48
|
-
public parent
|
|
51
|
+
public parent?: HTMLElement;
|
|
49
52
|
|
|
50
53
|
/**
|
|
51
54
|
* Mounted view result containing:
|
|
@@ -54,7 +57,7 @@ export class View<TTags extends Record<string, HTMLElement>> extends Lifecycle i
|
|
|
54
57
|
*
|
|
55
58
|
* This is expected to be assigned by subclasses (or a mount helper) before {@link getView} is called.
|
|
56
59
|
*/
|
|
57
|
-
public view
|
|
60
|
+
public view?: MountViewResult<TTags>;
|
|
58
61
|
|
|
59
62
|
/**
|
|
60
63
|
* Creates a View bound to the specified parent container and initializes lifecycle state.
|
|
@@ -79,7 +82,9 @@ export class View<TTags extends Record<string, HTMLElement>> extends Lifecycle i
|
|
|
79
82
|
*/
|
|
80
83
|
public getView(): HTMLElement {
|
|
81
84
|
if (!this.view?.view) {
|
|
82
|
-
throw new Error(
|
|
85
|
+
throw new Error(
|
|
86
|
+
"View is not mounted. Did you forget to set this.view?",
|
|
87
|
+
);
|
|
83
88
|
}
|
|
84
89
|
return this.view.view;
|
|
85
90
|
}
|
|
@@ -108,4 +113,4 @@ export class View<TTags extends Record<string, HTMLElement>> extends Lifecycle i
|
|
|
108
113
|
|
|
109
114
|
super.destroy();
|
|
110
115
|
}
|
|
111
|
-
}
|
|
116
|
+
}
|
|
@@ -2,7 +2,10 @@ import { ModelContract } from "src/ts/types/core/base/model.type";
|
|
|
2
2
|
import { RecyclerView } from "./recyclerview";
|
|
3
3
|
import { AdapterContract } from "src/ts/types/core/base/adapter.type";
|
|
4
4
|
import { Libs } from "src/ts/utils/libs";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
VirtualOptions,
|
|
7
|
+
VirtualRecyclerViewTags,
|
|
8
|
+
} from "src/ts/types/core/base/virtual-recyclerview.type";
|
|
6
9
|
import { Lifecycle } from "./lifecycle";
|
|
7
10
|
import { LifecycleState } from "src/ts/types/core/base/lifecycle.type";
|
|
8
11
|
import { Fenwick } from "./fenwick";
|
|
@@ -58,7 +61,7 @@ import { Fenwick } from "./fenwick";
|
|
|
58
61
|
*/
|
|
59
62
|
export class VirtualRecyclerView<
|
|
60
63
|
TItem extends ModelContract<any, any>,
|
|
61
|
-
TAdapter extends AdapterContract<TItem
|
|
64
|
+
TAdapter extends AdapterContract<TItem>,
|
|
62
65
|
> extends RecyclerView<TItem, TAdapter> {
|
|
63
66
|
/**
|
|
64
67
|
* Virtualization settings (materialized to `Required<VirtualOptions>`).
|
|
@@ -109,8 +112,8 @@ export class VirtualRecyclerView<
|
|
|
109
112
|
private resizeObs?: ResizeObserver;
|
|
110
113
|
|
|
111
114
|
/** Pending animation frame ids for window and measurement. */
|
|
112
|
-
private rafId
|
|
113
|
-
private measureRaf
|
|
115
|
+
private rafId?: number;
|
|
116
|
+
private measureRaf?: number;
|
|
114
117
|
|
|
115
118
|
/** Re-entrancy/suspension flags used to prevent feedback loops. */
|
|
116
119
|
private updating = false;
|
|
@@ -138,9 +141,9 @@ export class VirtualRecyclerView<
|
|
|
138
141
|
*
|
|
139
142
|
* Note: The virtualization scaffold is built when an adapter is set via {@link setAdapter}.
|
|
140
143
|
*
|
|
141
|
-
* @param {HTMLDivElement
|
|
144
|
+
* @param {HTMLDivElement} [viewElement=null] - Optional root container for the recycler view.
|
|
142
145
|
*/
|
|
143
|
-
constructor(viewElement
|
|
146
|
+
constructor(viewElement?: HTMLDivElement) {
|
|
144
147
|
super(viewElement);
|
|
145
148
|
}
|
|
146
149
|
|
|
@@ -187,24 +190,37 @@ export class VirtualRecyclerView<
|
|
|
187
190
|
|
|
188
191
|
this.viewElement.replaceChildren();
|
|
189
192
|
|
|
190
|
-
const nodeMounted = Libs.mountNode(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
const nodeMounted = Libs.mountNode<VirtualRecyclerViewTags>(
|
|
194
|
+
{
|
|
195
|
+
PadTop: {
|
|
196
|
+
tag: { node: "div", classList: "seui-virtual-pad-top" },
|
|
197
|
+
},
|
|
198
|
+
ItemsHost: {
|
|
199
|
+
tag: { node: "div", classList: "seui-virtual-items" },
|
|
200
|
+
},
|
|
201
|
+
PadBottom: {
|
|
202
|
+
tag: { node: "div", classList: "seui-virtual-pad-bottom" },
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
this.viewElement,
|
|
206
|
+
);
|
|
195
207
|
|
|
196
208
|
this.PadTop = nodeMounted.PadTop;
|
|
197
209
|
this.ItemsHost = nodeMounted.ItemsHost;
|
|
198
210
|
this.PadBottom = nodeMounted.PadBottom;
|
|
199
211
|
|
|
200
|
-
this.scrollEl =
|
|
201
|
-
|
|
202
|
-
|
|
212
|
+
this.scrollEl =
|
|
213
|
+
this.opts.scrollEl ??
|
|
214
|
+
(this.viewElement.closest(".seui-popup") as HTMLElement) ??
|
|
215
|
+
(this.viewElement.parentElement as HTMLElement);
|
|
203
216
|
|
|
204
|
-
if (!this.scrollEl)
|
|
217
|
+
if (!this.scrollEl)
|
|
218
|
+
throw new Error("VirtualRecyclerView: scrollEl not found");
|
|
205
219
|
|
|
206
220
|
this.boundOnScroll = this.onScroll.bind(this);
|
|
207
|
-
this.scrollEl.addEventListener("scroll", this.boundOnScroll, {
|
|
221
|
+
this.scrollEl.addEventListener("scroll", this.boundOnScroll, {
|
|
222
|
+
passive: true,
|
|
223
|
+
});
|
|
208
224
|
|
|
209
225
|
this.refresh(false);
|
|
210
226
|
this.attachResizeObserverOnce();
|
|
@@ -249,7 +265,9 @@ export class VirtualRecyclerView<
|
|
|
249
265
|
this.suspended = false;
|
|
250
266
|
|
|
251
267
|
if (this.scrollEl && this.boundOnScroll) {
|
|
252
|
-
this.scrollEl.addEventListener("scroll", this.boundOnScroll, {
|
|
268
|
+
this.scrollEl.addEventListener("scroll", this.boundOnScroll, {
|
|
269
|
+
passive: true,
|
|
270
|
+
});
|
|
253
271
|
}
|
|
254
272
|
|
|
255
273
|
if (this.resumeResizeAfter) {
|
|
@@ -310,7 +328,10 @@ export class VirtualRecyclerView<
|
|
|
310
328
|
* @param {{ scrollIntoView?: boolean }} [opt] - Optional behavior controls.
|
|
311
329
|
* @returns {void}
|
|
312
330
|
*/
|
|
313
|
-
public ensureRendered(
|
|
331
|
+
public ensureRendered(
|
|
332
|
+
index: number,
|
|
333
|
+
opt?: { scrollIntoView?: boolean },
|
|
334
|
+
): void {
|
|
314
335
|
this.mountRange(index, index);
|
|
315
336
|
if (opt?.scrollIntoView) this.scrollToIndex(index);
|
|
316
337
|
}
|
|
@@ -334,7 +355,10 @@ export class VirtualRecyclerView<
|
|
|
334
355
|
const topInContainer = this.offsetTopOf(index);
|
|
335
356
|
const containerTop = this.containerTopInScroll();
|
|
336
357
|
const target = containerTop + topInContainer;
|
|
337
|
-
const maxScroll = Math.max(
|
|
358
|
+
const maxScroll = Math.max(
|
|
359
|
+
0,
|
|
360
|
+
this.scrollEl.scrollHeight - this.scrollEl.clientHeight,
|
|
361
|
+
);
|
|
338
362
|
|
|
339
363
|
this.scrollEl.scrollTop = Math.min(Math.max(0, target), maxScroll);
|
|
340
364
|
}
|
|
@@ -358,7 +382,7 @@ export class VirtualRecyclerView<
|
|
|
358
382
|
}
|
|
359
383
|
|
|
360
384
|
this.resizeObs?.disconnect();
|
|
361
|
-
this.created.forEach(el => el.remove());
|
|
385
|
+
this.created.forEach((el) => el.remove());
|
|
362
386
|
this.created.clear();
|
|
363
387
|
}
|
|
364
388
|
|
|
@@ -442,7 +466,7 @@ export class VirtualRecyclerView<
|
|
|
442
466
|
* @returns {void}
|
|
443
467
|
*/
|
|
444
468
|
private resetState(): void {
|
|
445
|
-
this.created.forEach(el => el.remove());
|
|
469
|
+
this.created.forEach((el) => el.remove());
|
|
446
470
|
this.created.clear();
|
|
447
471
|
this.heightCache = [];
|
|
448
472
|
this.fenwick.reset(0);
|
|
@@ -557,7 +581,9 @@ export class VirtualRecyclerView<
|
|
|
557
581
|
const now = performance.now();
|
|
558
582
|
if (now - this.stickyCacheTick < 16) return this.stickyCacheVal;
|
|
559
583
|
|
|
560
|
-
const sticky = this.scrollEl.querySelector(
|
|
584
|
+
const sticky = this.scrollEl.querySelector(
|
|
585
|
+
".seui-option-handle:not(.hide)",
|
|
586
|
+
) as HTMLElement | null;
|
|
561
587
|
this.stickyCacheVal = sticky?.offsetHeight ?? 0;
|
|
562
588
|
this.stickyCacheTick = now;
|
|
563
589
|
return this.stickyCacheVal;
|
|
@@ -623,7 +649,7 @@ export class VirtualRecyclerView<
|
|
|
623
649
|
private rebuildFenwick(count: number): void {
|
|
624
650
|
const est = this.getEstimate();
|
|
625
651
|
const arr = Array.from({ length: count }, (_, i) =>
|
|
626
|
-
this.isIndexVisible(i) ? (this.heightCache[i] ?? est) : 0
|
|
652
|
+
this.isIndexVisible(i) ? (this.heightCache[i] ?? est) : 0,
|
|
627
653
|
);
|
|
628
654
|
this.fenwick.buildFrom(arr);
|
|
629
655
|
}
|
|
@@ -738,8 +764,12 @@ export class VirtualRecyclerView<
|
|
|
738
764
|
const next = el.nextElementSibling as HTMLElement | null;
|
|
739
765
|
|
|
740
766
|
const needsReorder =
|
|
741
|
-
(prev &&
|
|
742
|
-
|
|
767
|
+
(prev &&
|
|
768
|
+
Number(prev.getAttribute(VirtualRecyclerView.ATTR_INDEX)) >
|
|
769
|
+
index) ||
|
|
770
|
+
(next &&
|
|
771
|
+
Number(next.getAttribute(VirtualRecyclerView.ATTR_INDEX)) <
|
|
772
|
+
index);
|
|
743
773
|
|
|
744
774
|
if (needsReorder) {
|
|
745
775
|
el.remove();
|
|
@@ -763,7 +793,13 @@ export class VirtualRecyclerView<
|
|
|
763
793
|
if (this.resizeObs) return;
|
|
764
794
|
|
|
765
795
|
this.resizeObs = new ResizeObserver(() => {
|
|
766
|
-
if (
|
|
796
|
+
if (
|
|
797
|
+
this.suppressResize ||
|
|
798
|
+
this.suspended ||
|
|
799
|
+
!this.adapter ||
|
|
800
|
+
this.measureRaf != null
|
|
801
|
+
)
|
|
802
|
+
return;
|
|
767
803
|
|
|
768
804
|
this.measureRaf = requestAnimationFrame(() => {
|
|
769
805
|
this.measureRaf = null;
|
|
@@ -794,7 +830,9 @@ export class VirtualRecyclerView<
|
|
|
794
830
|
if (!this.isIndexVisible(i)) continue;
|
|
795
831
|
|
|
796
832
|
const item = this.adapter.items[i];
|
|
797
|
-
const el = (item as any)?.view?.getView?.() as
|
|
833
|
+
const el = (item as any)?.view?.getView?.() as
|
|
834
|
+
| HTMLElement
|
|
835
|
+
| undefined;
|
|
798
836
|
if (!el) continue;
|
|
799
837
|
|
|
800
838
|
const newH = this.measureOuterHeight(el);
|
|
@@ -857,7 +895,8 @@ export class VirtualRecyclerView<
|
|
|
857
895
|
|
|
858
896
|
const anchorIndex = this.findFirstVisibleIndex(stRel, count);
|
|
859
897
|
const anchorTop = this.offsetTopOf(anchorIndex);
|
|
860
|
-
const anchorDelta =
|
|
898
|
+
const anchorDelta =
|
|
899
|
+
containerTop + anchorTop - this.scrollEl.scrollTop;
|
|
861
900
|
|
|
862
901
|
const firstVis = this.findFirstVisibleIndex(stRel, count);
|
|
863
902
|
if (firstVis === -1) {
|
|
@@ -868,12 +907,21 @@ export class VirtualRecyclerView<
|
|
|
868
907
|
const est = this.getEstimate();
|
|
869
908
|
const overscanPx = this.opts.overscan * est;
|
|
870
909
|
|
|
871
|
-
let startIndex =
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
910
|
+
let startIndex =
|
|
911
|
+
this.nextVisibleFrom(
|
|
912
|
+
Math.min(
|
|
913
|
+
count - 1,
|
|
914
|
+
this.fenwick.lowerBoundPrefix(
|
|
915
|
+
Math.max(0, stRel - overscanPx),
|
|
916
|
+
),
|
|
917
|
+
),
|
|
918
|
+
count,
|
|
919
|
+
) ?? firstVis;
|
|
920
|
+
|
|
921
|
+
let endIndex = Math.min(
|
|
922
|
+
count - 1,
|
|
923
|
+
this.fenwick.lowerBoundPrefix(stRel + vhEff + overscanPx),
|
|
924
|
+
);
|
|
877
925
|
|
|
878
926
|
if (startIndex === this.start && endIndex === this.end) return;
|
|
879
927
|
|
|
@@ -900,8 +948,12 @@ export class VirtualRecyclerView<
|
|
|
900
948
|
|
|
901
949
|
// Keep anchor item stable to prevent scroll jump
|
|
902
950
|
const anchorTopNew = this.offsetTopOf(anchorIndex);
|
|
903
|
-
const targetScroll =
|
|
904
|
-
|
|
951
|
+
const targetScroll =
|
|
952
|
+
this.containerTopInScroll() + anchorTopNew - anchorDelta;
|
|
953
|
+
const maxScroll = Math.max(
|
|
954
|
+
0,
|
|
955
|
+
this.scrollEl.scrollHeight - this.scrollEl.clientHeight,
|
|
956
|
+
);
|
|
905
957
|
const clamped = Math.min(Math.max(0, targetScroll), maxScroll);
|
|
906
958
|
|
|
907
959
|
const heightChanged = Math.abs(anchorTopNew - anchorTop) > 1;
|
|
@@ -1036,4 +1088,4 @@ export class VirtualRecyclerView<
|
|
|
1036
1088
|
private totalHeight(count: number): number {
|
|
1037
1089
|
return this.fenwick.sum(count);
|
|
1038
1090
|
}
|
|
1039
|
-
}
|
|
1091
|
+
}
|