selective-ui 1.1.6 → 1.2.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 +7 -1
- package/dist/selective-ui.css.map +1 -1
- package/dist/selective-ui.esm.js +794 -57
- 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 +795 -58
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/css/components/popup.css +7 -1
- package/src/ts/adapter/mixed-adapter.ts +29 -18
- package/src/ts/components/empty-state.ts +5 -4
- package/src/ts/components/loading-state.ts +4 -4
- package/src/ts/components/option-handle.ts +4 -4
- package/src/ts/components/popup.ts +35 -6
- package/src/ts/components/searchbox.ts +2 -0
- package/src/ts/components/selectbox.ts +23 -9
- package/src/ts/core/base/adapter.ts +8 -5
- package/src/ts/core/base/model.ts +19 -1
- package/src/ts/core/base/recyclerview.ts +3 -1
- package/src/ts/core/base/virtual-recyclerview.ts +763 -0
- package/src/ts/core/model-manager.ts +24 -16
- package/src/ts/core/search-controller.ts +5 -8
- package/src/ts/models/option-model.ts +22 -3
- package/src/ts/services/effector.ts +7 -7
- package/src/ts/types/components/state.box.type.ts +1 -18
- package/src/ts/types/core/base/adapter.type.ts +14 -0
- package/src/ts/types/core/base/model.type.ts +5 -0
- package/src/ts/types/core/base/recyclerview.type.ts +3 -1
- package/src/ts/types/core/base/view.type.ts +6 -0
- package/src/ts/types/core/base/virtual-recyclerview.type.ts +66 -0
- package/src/ts/types/utils/istorage.type.ts +1 -0
- package/src/ts/utils/istorage.ts +3 -2
- package/src/ts/utils/libs.ts +26 -25
- package/src/ts/utils/selective.ts +7 -7
- package/src/ts/views/option-view.ts +8 -8
package/package.json
CHANGED
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
display: flex;
|
|
29
29
|
flex-direction: column;
|
|
30
30
|
width: 100%;
|
|
31
|
-
gap: 2px;
|
|
32
31
|
|
|
33
32
|
-webkit-overflow-scrolling: touch;
|
|
34
33
|
touch-action: pan-y;
|
|
@@ -36,4 +35,11 @@
|
|
|
36
35
|
|
|
37
36
|
.selective-ui-options-container.hide {
|
|
38
37
|
display: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.selective-ui-options-container .selective-ui-virtual-items {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
width: 100%;
|
|
44
|
+
gap: 2px;
|
|
39
45
|
}
|
|
@@ -5,6 +5,8 @@ import { GroupView } from "../views/group-view";
|
|
|
5
5
|
import { OptionView } from "../views/option-view";
|
|
6
6
|
import { MixedItem, VisibilityStats } from "../types/core/base/mixed-adapter.type";
|
|
7
7
|
import { IEventCallback } from "../types/utils/ievents.type";
|
|
8
|
+
import { ImagePosition, LabelHalign, LabelValign } from "../types/views/view.option.type";
|
|
9
|
+
import { Libs } from "../utils/libs";
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* @extends {Adapter<GroupModel|OptionModel>}
|
|
@@ -24,6 +26,21 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
24
26
|
constructor(items: MixedItem[] = []) {
|
|
25
27
|
super(items);
|
|
26
28
|
this._buildFlatStructure();
|
|
29
|
+
|
|
30
|
+
Libs.callbackScheduler.on(`sche_vis_${this.adapterKey}`, () => {
|
|
31
|
+
const visibleCount = this.flatOptions.filter((item) => item.visible).length;
|
|
32
|
+
const totalCount = this.flatOptions.length;
|
|
33
|
+
|
|
34
|
+
this._visibilityChangedCallbacks.forEach((callback) => {
|
|
35
|
+
callback({
|
|
36
|
+
visibleCount,
|
|
37
|
+
totalCount,
|
|
38
|
+
hasVisible: visibleCount > 0,
|
|
39
|
+
isEmpty: totalCount === 0,
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
Libs.callbackScheduler.run(`sche_vis_proxy_${this.adapterKey}`);
|
|
43
|
+
}, {debounce: 10});
|
|
27
44
|
}
|
|
28
45
|
|
|
29
46
|
/**
|
|
@@ -84,7 +101,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
84
101
|
* @param {number} position - The position (index) of the group within a list.
|
|
85
102
|
*/
|
|
86
103
|
private _handleGroupView(groupModel: GroupModel, groupView: GroupView, position: number): void {
|
|
87
|
-
super.onViewHolder(groupModel
|
|
104
|
+
super.onViewHolder(groupModel, groupView, position);
|
|
88
105
|
groupModel.view = groupView;
|
|
89
106
|
|
|
90
107
|
const header = groupView.view.tags.GroupHeader;
|
|
@@ -141,13 +158,13 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
141
158
|
imageWidth: optionModel.options.imageWidth as string,
|
|
142
159
|
imageHeight: optionModel.options.imageHeight as string,
|
|
143
160
|
imageBorderRadius: optionModel.options.imageBorderRadius as string,
|
|
144
|
-
imagePosition: optionModel.options.imagePosition as
|
|
145
|
-
labelValign: optionModel.options.labelValign as
|
|
146
|
-
labelHalign: optionModel.options.labelHalign as
|
|
161
|
+
imagePosition: optionModel.options.imagePosition as ImagePosition,
|
|
162
|
+
labelValign: optionModel.options.labelValign as LabelValign,
|
|
163
|
+
labelHalign: optionModel.options.labelHalign as LabelHalign,
|
|
147
164
|
};
|
|
148
165
|
|
|
149
166
|
if (!optionModel.isInit) {
|
|
150
|
-
super.onViewHolder(optionModel
|
|
167
|
+
super.onViewHolder(optionModel, optionViewer, position);
|
|
151
168
|
}
|
|
152
169
|
|
|
153
170
|
optionModel.view = optionViewer;
|
|
@@ -288,17 +305,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
288
305
|
* Computes visible and total counts, then emits aggregated state.
|
|
289
306
|
*/
|
|
290
307
|
private _notifyVisibilityChanged(): void {
|
|
291
|
-
|
|
292
|
-
const totalCount = this.flatOptions.length;
|
|
293
|
-
|
|
294
|
-
this._visibilityChangedCallbacks.forEach((callback) => {
|
|
295
|
-
callback({
|
|
296
|
-
visibleCount,
|
|
297
|
-
totalCount,
|
|
298
|
-
hasVisible: visibleCount > 0,
|
|
299
|
-
isEmpty: totalCount === 0,
|
|
300
|
-
});
|
|
301
|
-
});
|
|
308
|
+
Libs.callbackScheduler.run(`sche_vis_${this.adapterKey}`);
|
|
302
309
|
}
|
|
303
310
|
|
|
304
311
|
/**
|
|
@@ -387,14 +394,18 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
387
394
|
|
|
388
395
|
for (let i = index; i < this.flatOptions.length; i++) {
|
|
389
396
|
const item = this.flatOptions[i];
|
|
390
|
-
if (!item
|
|
397
|
+
if (!item?.visible) continue;
|
|
391
398
|
|
|
392
399
|
item.highlighted = true;
|
|
393
400
|
this._currentHighlightIndex = i;
|
|
394
401
|
|
|
395
402
|
if (isScrollToView) {
|
|
396
403
|
const el = item.view?.getView?.();
|
|
397
|
-
if (el)
|
|
404
|
+
if (el) {
|
|
405
|
+
el.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
406
|
+
} else {
|
|
407
|
+
this.recyclerView?.ensureRendered?.(i, { scrollIntoView: true });
|
|
408
|
+
}
|
|
398
409
|
}
|
|
399
410
|
|
|
400
411
|
this.onHighlightChange(i, item.view?.getView?.()?.id);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EmptyStateType } from "../types/components/state.box.type";
|
|
2
|
+
import { SelectiveOptions } from "../types/utils/selective.type";
|
|
2
3
|
import { Libs } from "../utils/libs";
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -7,13 +8,13 @@ import { Libs } from "../utils/libs";
|
|
|
7
8
|
export class EmptyState {
|
|
8
9
|
node: HTMLDivElement | null = null;
|
|
9
10
|
|
|
10
|
-
options:
|
|
11
|
+
options: SelectiveOptions | null = null;
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Represents an empty state component that displays a message when no data or search results are available.
|
|
14
15
|
* Provides methods to show/hide the state and check its visibility.
|
|
15
16
|
*/
|
|
16
|
-
constructor(options:
|
|
17
|
+
constructor(options: SelectiveOptions | null = null) {
|
|
17
18
|
if (options) this.init(options);
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -22,7 +23,7 @@ export class EmptyState {
|
|
|
22
23
|
*
|
|
23
24
|
* @param {object} options - Configuration object containing text for "no data" and "not found" states.
|
|
24
25
|
*/
|
|
25
|
-
init(options:
|
|
26
|
+
init(options: SelectiveOptions): void {
|
|
26
27
|
this.options = options;
|
|
27
28
|
|
|
28
29
|
this.node = Libs.nodeCreator({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SelectiveOptions } from "../types/utils/selective.type";
|
|
2
2
|
import { Libs } from "../utils/libs";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -7,13 +7,13 @@ import { Libs } from "../utils/libs";
|
|
|
7
7
|
export class LoadingState {
|
|
8
8
|
node: HTMLDivElement | null = null;
|
|
9
9
|
|
|
10
|
-
options:
|
|
10
|
+
options: SelectiveOptions | null = null;
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Represents a loading state component that displays a loading message during data fetch or processing.
|
|
14
14
|
* Provides methods to show/hide the state and check its visibility.
|
|
15
15
|
*/
|
|
16
|
-
constructor(options:
|
|
16
|
+
constructor(options: SelectiveOptions | null = null) {
|
|
17
17
|
if (options) this.init(options);
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -22,7 +22,7 @@ export class LoadingState {
|
|
|
22
22
|
*
|
|
23
23
|
* @param {object} options - Configuration object containing text for the loading message.
|
|
24
24
|
*/
|
|
25
|
-
init(options:
|
|
25
|
+
init(options: SelectiveOptions): void {
|
|
26
26
|
this.options = options;
|
|
27
27
|
|
|
28
28
|
this.node = Libs.nodeCreator({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DefaultConfig } from "../types/utils/istorage.type";
|
|
2
1
|
import { MountViewResult } from "../types/utils/libs.type";
|
|
2
|
+
import { SelectiveOptions } from "../types/utils/selective.type";
|
|
3
3
|
import { iEvents } from "../utils/ievents";
|
|
4
4
|
import { Libs } from "../utils/libs";
|
|
5
5
|
|
|
@@ -11,7 +11,7 @@ export class OptionHandle {
|
|
|
11
11
|
|
|
12
12
|
node: HTMLDivElement | null = null;
|
|
13
13
|
|
|
14
|
-
options:
|
|
14
|
+
options: SelectiveOptions | null = null;
|
|
15
15
|
|
|
16
16
|
private _ActionOnSelectAll: Array<(...args: unknown[]) => unknown> = [];
|
|
17
17
|
|
|
@@ -22,7 +22,7 @@ export class OptionHandle {
|
|
|
22
22
|
* for multiple-selection lists. Includes methods to show/hide the handle, refresh its visibility,
|
|
23
23
|
* and register callbacks for select/deselect events.
|
|
24
24
|
*/
|
|
25
|
-
constructor(options:
|
|
25
|
+
constructor(options: SelectiveOptions | null = null) {
|
|
26
26
|
if (options) this.init(options);
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -32,7 +32,7 @@ export class OptionHandle {
|
|
|
32
32
|
*
|
|
33
33
|
* @param {object} options - Configuration object containing text labels and feature flags.
|
|
34
34
|
*/
|
|
35
|
-
init(options:
|
|
35
|
+
init(options: SelectiveOptions): void {
|
|
36
36
|
this.nodeMounted = Libs.mountNode({
|
|
37
37
|
OptionHandle: {
|
|
38
38
|
tag: { node: "div", classList: ["selective-ui-option-handle", "hide"] },
|
|
@@ -20,6 +20,13 @@ type ParentBinderMapLike = {
|
|
|
20
20
|
[key: string]: unknown;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
interface VirtualRecyclerOptions {
|
|
24
|
+
scrollEl?: HTMLElement;
|
|
25
|
+
estimateItemHeight?: number;
|
|
26
|
+
overscan?: number;
|
|
27
|
+
dynamicHeights?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
23
30
|
/**
|
|
24
31
|
* @class
|
|
25
32
|
*/
|
|
@@ -54,6 +61,12 @@ export class Popup {
|
|
|
54
61
|
|
|
55
62
|
private _hideLoadHandle: ReturnType<typeof setTimeout> | null = null;
|
|
56
63
|
|
|
64
|
+
private virtualScrollConfig = {
|
|
65
|
+
estimateItemHeight: 36,
|
|
66
|
+
overscan: 8,
|
|
67
|
+
dynamicHeights: true
|
|
68
|
+
};
|
|
69
|
+
|
|
57
70
|
/**
|
|
58
71
|
* Represents a popup component that manages rendering and interaction for a dropdown panel.
|
|
59
72
|
* Stores a reference to the ModelManager for handling option models and adapter logic.
|
|
@@ -84,9 +97,9 @@ export class Popup {
|
|
|
84
97
|
init(select: HTMLSelectElement, options: SelectiveOptions): void {
|
|
85
98
|
if (!this._modelManager) throw new Error("Popup requires a ModelManager instance.");
|
|
86
99
|
|
|
87
|
-
this.optionHandle = new OptionHandle(options
|
|
88
|
-
this.emptyState = new EmptyState(options
|
|
89
|
-
this.loadingState = new LoadingState(options
|
|
100
|
+
this.optionHandle = new OptionHandle(options);
|
|
101
|
+
this.emptyState = new EmptyState(options);
|
|
102
|
+
this.loadingState = new LoadingState(options);
|
|
90
103
|
|
|
91
104
|
const nodeMounted = Libs.mountNode(
|
|
92
105
|
{
|
|
@@ -112,7 +125,7 @@ export class Popup {
|
|
|
112
125
|
},
|
|
113
126
|
},
|
|
114
127
|
null
|
|
115
|
-
)
|
|
128
|
+
);
|
|
116
129
|
|
|
117
130
|
this.node = nodeMounted.view as HTMLDivElement;
|
|
118
131
|
this._optionsContainer = nodeMounted.tags.OptionsContainer as HTMLDivElement;
|
|
@@ -120,8 +133,19 @@ export class Popup {
|
|
|
120
133
|
this._parent = Libs.getBinderMap(select) as ParentBinderMapLike | null;
|
|
121
134
|
this.options = options;
|
|
122
135
|
|
|
136
|
+
|
|
137
|
+
const recyclerViewOpt = options.virtualScroll
|
|
138
|
+
? {
|
|
139
|
+
scrollEl: this.node,
|
|
140
|
+
estimateItemHeight: this.virtualScrollConfig.estimateItemHeight,
|
|
141
|
+
overscan: this.virtualScrollConfig.overscan,
|
|
142
|
+
dynamicHeights: this.virtualScrollConfig.dynamicHeights }
|
|
143
|
+
: {}
|
|
144
|
+
;
|
|
145
|
+
|
|
146
|
+
|
|
123
147
|
// Load ModelManager resources into container
|
|
124
|
-
this._modelManager.load(this._optionsContainer, { isMultiple: options.multiple });
|
|
148
|
+
this._modelManager.load<VirtualRecyclerOptions>(this._optionsContainer, { isMultiple: options.multiple }, recyclerViewOpt);
|
|
125
149
|
|
|
126
150
|
const MMResources = this._modelManager.getResources() as {
|
|
127
151
|
adapter: MixedAdapter;
|
|
@@ -294,6 +318,9 @@ export class Popup {
|
|
|
294
318
|
|
|
295
319
|
this._resizeObser.connect(this._parent.container.tags.ViewPanel);
|
|
296
320
|
callback?.();
|
|
321
|
+
|
|
322
|
+
const rv: any = this.recyclerView;
|
|
323
|
+
rv?.resume?.();
|
|
297
324
|
},
|
|
298
325
|
});
|
|
299
326
|
}
|
|
@@ -304,6 +331,8 @@ export class Popup {
|
|
|
304
331
|
*/
|
|
305
332
|
close(callback: (() => void) | null = null): void {
|
|
306
333
|
if (!this.isCreated || !this.options || !this._resizeObser || !this._effSvc) return;
|
|
334
|
+
const rv: any = this.recyclerView;
|
|
335
|
+
rv?.suspend?.();
|
|
307
336
|
|
|
308
337
|
this._resizeObser.disconnect();
|
|
309
338
|
this._effSvc.collapse({
|
|
@@ -388,7 +417,7 @@ export class Popup {
|
|
|
388
417
|
this._resizeObser = null;
|
|
389
418
|
|
|
390
419
|
try {
|
|
391
|
-
this._effSvc?.setElement?.(null
|
|
420
|
+
this._effSvc?.setElement?.(null);
|
|
392
421
|
} catch (_) {}
|
|
393
422
|
this._effSvc = null;
|
|
394
423
|
|
|
@@ -24,6 +24,7 @@ import { BinderMap } from "../types/utils/istorage.type";
|
|
|
24
24
|
import { ContainerRuntime, SelectBoxAction } from "../types/components/searchbox.type";
|
|
25
25
|
import { AjaxConfig } from "../types/core/search-controller.type";
|
|
26
26
|
import { Selective } from "../utils/selective";
|
|
27
|
+
import { VirtualRecyclerView } from "../core/base/virtual-recyclerview";
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* @class
|
|
@@ -174,7 +175,12 @@ export class SelectBox {
|
|
|
174
175
|
|
|
175
176
|
// ModelManager setup
|
|
176
177
|
optionModelManager.setupAdapter(MixedAdapter);
|
|
177
|
-
|
|
178
|
+
if (options.virtualScroll) {
|
|
179
|
+
optionModelManager.setupRecyclerView(VirtualRecyclerView);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
optionModelManager.setupRecyclerView(RecyclerView);
|
|
183
|
+
}
|
|
178
184
|
optionModelManager.createModelResources(Libs.parseSelectToArray(select));
|
|
179
185
|
|
|
180
186
|
optionModelManager.onUpdated = () => {
|
|
@@ -245,6 +251,7 @@ export class SelectBox {
|
|
|
245
251
|
}
|
|
246
252
|
|
|
247
253
|
const optionAdapter = container.popup!.optionAdapter as MixedAdapter;
|
|
254
|
+
let hightlightTimer : ReturnType<typeof setTimeout> | null = null;
|
|
248
255
|
|
|
249
256
|
const searchHandle = (keyword: string, isTrigger: boolean) => {
|
|
250
257
|
if (!isTrigger && keyword === "") {
|
|
@@ -255,12 +262,17 @@ export class SelectBox {
|
|
|
255
262
|
searchController
|
|
256
263
|
.search(keyword)
|
|
257
264
|
.then((result: any) => {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
265
|
+
clearTimeout(hightlightTimer!);
|
|
266
|
+
Libs.callbackScheduler.on(`sche_vis_proxy_${optionAdapter.adapterKey}`, () => {
|
|
267
|
+
container.popup?.triggerResize?.();
|
|
268
|
+
|
|
269
|
+
if (result?.hasResults) {
|
|
270
|
+
hightlightTimer = setTimeout(() => {
|
|
271
|
+
optionAdapter.resetHighlight();
|
|
272
|
+
container.popup?.triggerResize?.();
|
|
273
|
+
}, options.animationtime ?? 0);
|
|
274
|
+
}
|
|
275
|
+
}, { debounce: 10 });
|
|
264
276
|
})
|
|
265
277
|
.catch((error: unknown) => {
|
|
266
278
|
console.error("Search error:", error);
|
|
@@ -272,15 +284,17 @@ export class SelectBox {
|
|
|
272
284
|
|
|
273
285
|
searchbox.onSearch = (keyword: string, isTrigger: boolean) => {
|
|
274
286
|
if (!searchController.compareSearchTrigger(keyword)) return;
|
|
287
|
+
if (searchHandleTimer) clearTimeout(searchHandleTimer);
|
|
275
288
|
|
|
276
289
|
if (searchController.isAjax()) {
|
|
277
|
-
if (searchHandleTimer) clearTimeout(searchHandleTimer);
|
|
278
290
|
container.popup?.showLoading?.();
|
|
279
291
|
searchHandleTimer = setTimeout(() => {
|
|
280
292
|
searchHandle(keyword, isTrigger);
|
|
281
293
|
}, options.delaysearchtime ?? 0);
|
|
282
294
|
} else {
|
|
283
|
-
|
|
295
|
+
searchHandleTimer = setTimeout(() => {
|
|
296
|
+
searchHandle(keyword, isTrigger);
|
|
297
|
+
}, 10);
|
|
284
298
|
}
|
|
285
299
|
};
|
|
286
300
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Libs } from "../../utils/libs";
|
|
2
2
|
import type { ModelContract } from "../../types/core/base/model.type";
|
|
3
3
|
import type { AdapterContract } from "../../types/core/base/adapter.type";
|
|
4
|
+
import { ViewContract } from "src/ts/types/core/base/view.type";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* @template TItem
|
|
@@ -9,13 +10,15 @@ import type { AdapterContract } from "../../types/core/base/adapter.type";
|
|
|
9
10
|
*/
|
|
10
11
|
export class Adapter<
|
|
11
12
|
TItem extends ModelContract<any, any> & { view: TViewer | null; isInit: boolean },
|
|
12
|
-
TViewer
|
|
13
|
+
TViewer extends ViewContract<any>
|
|
13
14
|
> implements AdapterContract<TItem> {
|
|
14
15
|
items: TItem[] = [];
|
|
15
16
|
|
|
16
17
|
adapterKey = Libs.randomString(12);
|
|
17
18
|
|
|
18
19
|
isSkipEvent = false;
|
|
20
|
+
|
|
21
|
+
recyclerView: any;
|
|
19
22
|
|
|
20
23
|
/**
|
|
21
24
|
* Initializes the adapter with an optional array of items and invokes onInit()
|
|
@@ -45,11 +48,11 @@ export class Adapter<
|
|
|
45
48
|
onViewHolder(item: TItem, viewer: TViewer | null, position: number): void {
|
|
46
49
|
void position;
|
|
47
50
|
|
|
48
|
-
const v = viewer
|
|
49
|
-
if (
|
|
50
|
-
v?.render?.();
|
|
51
|
-
} else {
|
|
51
|
+
const v = viewer;
|
|
52
|
+
if (item.isInit) {
|
|
52
53
|
v?.update?.();
|
|
54
|
+
} else {
|
|
55
|
+
v?.render?.();
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -13,7 +13,6 @@ export class Model<
|
|
|
13
13
|
TView extends ViewContract<TTags>,
|
|
14
14
|
TOptions = unknown
|
|
15
15
|
> implements ModelContract<TTarget, TView> {
|
|
16
|
-
/** @type {TTarget | null} */
|
|
17
16
|
targetElement: TTarget | null = null;
|
|
18
17
|
|
|
19
18
|
options: TOptions;
|
|
@@ -24,6 +23,8 @@ export class Model<
|
|
|
24
23
|
|
|
25
24
|
isInit = false;
|
|
26
25
|
|
|
26
|
+
isRemoved = false;
|
|
27
|
+
|
|
27
28
|
/**
|
|
28
29
|
* Returns the current value from the underlying target element's "value" attribute.
|
|
29
30
|
* For single-select, this is typically a string; for multi-select, may be an array depending on usage.
|
|
@@ -56,9 +57,26 @@ export class Model<
|
|
|
56
57
|
this.onTargetChanged();
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Cleans up references and invokes the removal hook when the model is no longer needed.
|
|
62
|
+
*/
|
|
63
|
+
remove() {
|
|
64
|
+
this.targetElement = null;
|
|
65
|
+
this.view?.getView()?.remove?.();
|
|
66
|
+
this.view = null;
|
|
67
|
+
this.isRemoved = true;
|
|
68
|
+
this.onRemove();
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
/**
|
|
60
72
|
* Hook invoked whenever the target element changes.
|
|
61
73
|
* Override in subclasses to react to attribute/content updates (e.g., text, disabled state).
|
|
62
74
|
*/
|
|
63
75
|
onTargetChanged(): void { }
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Hook invoked whenever the target element is removed.
|
|
79
|
+
* Override in subclasses to react to removal of the element.
|
|
80
|
+
*/
|
|
81
|
+
onRemove(): void {}
|
|
64
82
|
}
|
|
@@ -76,8 +76,10 @@ export class RecyclerView<
|
|
|
76
76
|
/**
|
|
77
77
|
* Forces a re-render of the current adapter state into the container.
|
|
78
78
|
* Useful when visual updates are required without changing the data.
|
|
79
|
+
*
|
|
80
|
+
* @param isUpdate - Indicates if this refresh is due to an update operation.
|
|
79
81
|
*/
|
|
80
|
-
refresh(): void {
|
|
82
|
+
refresh(isUpdate: boolean): void {
|
|
81
83
|
this.render();
|
|
82
84
|
}
|
|
83
85
|
}
|