selective-ui 1.2.4 → 1.2.6
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.md +7 -0
- package/dist/selective-ui.css +64 -58
- package/dist/selective-ui.css.map +1 -1
- package/dist/selective-ui.esm.js +4396 -1344
- 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 +4401 -1345
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +3 -3
- package/src/css/components/accessorybox.css +1 -1
- package/src/css/components/directive.css +2 -2
- package/src/css/components/option-handle.css +4 -4
- package/src/css/components/placeholder.css +1 -1
- package/src/css/components/popup/empty-state.css +3 -3
- package/src/css/components/popup/loading-state.css +3 -3
- package/src/css/components/popup/popup.css +5 -5
- package/src/css/components/searchbox.css +2 -2
- package/src/css/components/selectbox.css +7 -7
- package/src/css/views/group-view.css +8 -8
- package/src/css/views/option-view.css +22 -22
- package/src/ts/adapter/mixed-adapter.ts +248 -92
- package/src/ts/components/accessorybox.ts +170 -73
- package/src/ts/components/directive.ts +55 -26
- package/src/ts/components/option-handle.ts +127 -60
- package/src/ts/components/placeholder.ts +73 -35
- package/src/ts/components/popup/empty-state.ts +71 -35
- package/src/ts/components/popup/loading-state.ts +73 -33
- package/src/ts/components/popup/popup.ts +19 -39
- package/src/ts/components/searchbox.ts +189 -50
- package/src/ts/components/selectbox.ts +401 -40
- package/src/ts/core/base/adapter.ts +160 -79
- package/src/ts/core/base/fenwick.ts +147 -0
- package/src/ts/core/base/lifecycle.ts +118 -35
- package/src/ts/core/base/model.ts +94 -36
- package/src/ts/core/base/recyclerview.ts +0 -1
- package/src/ts/core/base/view.ts +54 -23
- package/src/ts/core/base/virtual-recyclerview.ts +365 -283
- package/src/ts/core/model-manager.ts +172 -92
- package/src/ts/core/search-controller.ts +166 -93
- package/src/ts/global.ts +26 -5
- package/src/ts/index.ts +22 -3
- package/src/ts/models/group-model.ts +138 -32
- package/src/ts/models/option-model.ts +197 -53
- package/src/ts/services/dataset-observer.ts +72 -10
- package/src/ts/services/ea-observer.ts +87 -10
- package/src/ts/services/effector.ts +181 -32
- package/src/ts/services/refresher.ts +32 -7
- package/src/ts/services/resize-observer.ts +136 -19
- package/src/ts/services/select-observer.ts +115 -50
- package/src/ts/types/core/base/view.type.ts +3 -3
- package/src/ts/types/core/base/virtual-recyclerview.type.ts +1 -1
- package/src/ts/types/plugins/plugin.type.ts +46 -0
- package/src/ts/types/utils/ievents.type.ts +6 -1
- package/src/ts/types/utils/istorage.type.ts +8 -4
- package/src/ts/types/utils/libs.type.ts +2 -2
- package/src/ts/types/utils/selective.type.ts +14 -1
- package/src/ts/utils/callback-scheduler.ts +115 -37
- package/src/ts/utils/ievents.ts +91 -29
- package/src/ts/utils/libs.ts +41 -65
- package/src/ts/utils/selective.ts +412 -79
- package/src/ts/views/group-view.ts +142 -31
- package/src/ts/views/option-view.ts +272 -60
|
@@ -10,46 +10,90 @@ import { Libs } from "../utils/libs";
|
|
|
10
10
|
import { LifecycleState } from "../types/core/base/lifecycle.type";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Mixed (heterogeneous) adapter for rendering and interacting with a list that contains
|
|
14
|
+
* both {@link GroupModel} and {@link OptionModel} items.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* -
|
|
16
|
+
* ### Responsibility
|
|
17
|
+
* - Flatten hierarchical data (groups → options) into `flatOptions` for:
|
|
18
|
+
* - keyboard navigation (highlight + next/prev visible),
|
|
19
|
+
* - visibility aggregation (visibleCount/totalCount),
|
|
20
|
+
* - selection helpers (getSelectedItem(s), checkAll).
|
|
21
|
+
* - Create the correct view implementation per item:
|
|
22
|
+
* - {@link GroupView} for groups,
|
|
23
|
+
* - {@link OptionView} for options (including options inside a group container).
|
|
24
|
+
* - Bind DOM events and model hooks to keep View ↔ Model synchronized:
|
|
25
|
+
* - click → selection changes,
|
|
26
|
+
* - mouseenter → highlight changes,
|
|
27
|
+
* - model visibility change → group visibility recalculation + debounced stats event.
|
|
21
28
|
*
|
|
22
|
-
* Lifecycle
|
|
23
|
-
* -
|
|
24
|
-
* -
|
|
25
|
-
* -
|
|
29
|
+
* ### Lifecycle (Strict FSM, idempotency)
|
|
30
|
+
* - `init()` registers a debounced visibility aggregation job and then calls `mount()`.
|
|
31
|
+
* - View binding in `handleGroupView` / `handleOptionView` is guarded by `model.isInit`
|
|
32
|
+
* to avoid double-wiring listeners (idempotent binding).
|
|
33
|
+
* - `destroy()` clears scheduler jobs and destroys groups (cascades into their options/views),
|
|
34
|
+
* then transitions to `DESTROYED`. Subsequent destroy calls are **no-ops**.
|
|
26
35
|
*
|
|
27
|
-
*
|
|
36
|
+
* ### Event / Hook flow (external vs internal)
|
|
37
|
+
* - **External selection**: user click triggers `changingProp("select")` then changes `OptionModel.selected`,
|
|
38
|
+
* and eventually emits adapter-level `changeProp("selected")` via `optionModel.onSelected`.
|
|
39
|
+
* - **Internal selection**: `OptionModel.selectedNonTrigger` / `onInternalSelected` updates internal state
|
|
40
|
+
* (e.g., cache `selectedItemSingle`) and emits adapter-level `changeProp("selected_internal")`
|
|
41
|
+
* without implying user intent.
|
|
42
|
+
*
|
|
43
|
+
* ### Visibility / Highlight / Navigation
|
|
44
|
+
* - Visibility is tracked per option model (`OptionModel.visible`), and groups can update their own
|
|
45
|
+
* derived visibility via `GroupModel.updateVisibility()`.
|
|
46
|
+
* - Highlight is tracked by flat index (`currentHighlightIndex`) and by model flag
|
|
47
|
+
* (`OptionModel.highlighted`), enabling view-level styling / a11y hooks.
|
|
48
|
+
*
|
|
49
|
+
* ### DOM side effects / a11y notes
|
|
50
|
+
* - Adds DOM listeners (`click`, `mouseenter`) on first bind only.
|
|
51
|
+
* - Uses `Element.scrollIntoView()` when highlighting with scrolling enabled.
|
|
52
|
+
* - When options are virtualized, falls back to `recyclerView.ensureRendered(i, { scrollIntoView: true })`
|
|
53
|
+
* before attempting to scroll.
|
|
54
|
+
*
|
|
55
|
+
* @extends {Adapter<MixedItem, GroupView | OptionView>}
|
|
56
|
+
* @see {@link GroupModel}
|
|
57
|
+
* @see {@link OptionModel}
|
|
58
|
+
* @see {@link GroupView}
|
|
59
|
+
* @see {@link OptionView}
|
|
28
60
|
*/
|
|
29
61
|
export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
30
62
|
/** Whether the adapter operates in multi-selection mode. */
|
|
31
63
|
public isMultiple = false;
|
|
32
64
|
|
|
33
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Subscribers for aggregated visibility statistics.
|
|
67
|
+
* Fired via a debounced scheduler to avoid repeated recomputation during batch updates.
|
|
68
|
+
*/
|
|
34
69
|
private visibilityChangedCallbacks: Array<(stats: VisibilityStats) => void> = [];
|
|
35
70
|
|
|
36
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Flat index of the currently highlighted option.
|
|
73
|
+
* `-1` indicates "no highlight".
|
|
74
|
+
*/
|
|
37
75
|
private currentHighlightIndex = -1;
|
|
38
76
|
|
|
39
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Cached pointer to the selected option in single-select mode.
|
|
79
|
+
* Used to efficiently clear previous selection when selecting a new option.
|
|
80
|
+
*/
|
|
40
81
|
private selectedItemSingle: OptionModel | null = null;
|
|
41
82
|
|
|
42
83
|
/** Top-level group models (if any). */
|
|
43
84
|
public groups: GroupModel[] = [];
|
|
44
85
|
|
|
45
|
-
/**
|
|
86
|
+
/**
|
|
87
|
+
* Flattened list of all option models, including options inside groups.
|
|
88
|
+
* This is the primary index space for navigation/highlight.
|
|
89
|
+
*/
|
|
46
90
|
public flatOptions: OptionModel[] = [];
|
|
47
91
|
|
|
48
92
|
/**
|
|
49
93
|
* Creates a MixedAdapter with an optional initial list of items.
|
|
50
|
-
*
|
|
94
|
+
* Immediately computes `groups` and `flatOptions` for navigation/stats.
|
|
51
95
|
*
|
|
52
|
-
* @param items - Initial items (groups and/or options).
|
|
96
|
+
* @param {MixedItem[]} [items=[]] - Initial items (groups and/or options).
|
|
53
97
|
*/
|
|
54
98
|
public constructor(items: MixedItem[] = []) {
|
|
55
99
|
super(items);
|
|
@@ -57,11 +101,19 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
57
101
|
}
|
|
58
102
|
|
|
59
103
|
/**
|
|
60
|
-
* Initializes
|
|
61
|
-
*
|
|
104
|
+
* Initializes debounced visibility aggregation and transitions lifecycle forward.
|
|
105
|
+
*
|
|
106
|
+
* - Registers `sche_vis_${adapterKey}`:
|
|
107
|
+
* - computes `{ visibleCount, totalCount, hasVisible, isEmpty }` from `flatOptions`,
|
|
108
|
+
* - notifies {@link onVisibilityChanged} subscribers,
|
|
109
|
+
* - triggers a proxy scheduler `sche_vis_proxy_${adapterKey}` for downstream chaining.
|
|
110
|
+
* - Calls base `init()` and mounts immediately.
|
|
62
111
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
112
|
+
* Idempotency:
|
|
113
|
+
* - Scheduler key is deterministic per adapter instance (`adapterKey`).
|
|
114
|
+
*
|
|
115
|
+
* @returns {void}
|
|
116
|
+
* @override
|
|
65
117
|
*/
|
|
66
118
|
public override init() {
|
|
67
119
|
Libs.callbackScheduler.on(
|
|
@@ -90,11 +142,16 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
90
142
|
}
|
|
91
143
|
|
|
92
144
|
/**
|
|
93
|
-
*
|
|
145
|
+
* Rebuilds the derived structures:
|
|
146
|
+
* - `groups`: top-level {@link GroupModel} list
|
|
147
|
+
* - `flatOptions`: all {@link OptionModel} instances in traversal order
|
|
94
148
|
*
|
|
95
149
|
* The flat list is used for:
|
|
96
|
-
* - navigation across visible options
|
|
97
|
-
* - computing visibility statistics
|
|
150
|
+
* - navigation across visible options,
|
|
151
|
+
* - computing visibility statistics,
|
|
152
|
+
* - highlight index mapping.
|
|
153
|
+
*
|
|
154
|
+
* @returns {void}
|
|
98
155
|
*/
|
|
99
156
|
private buildFlatStructure(): void {
|
|
100
157
|
this.flatOptions = [];
|
|
@@ -111,11 +168,12 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
111
168
|
}
|
|
112
169
|
|
|
113
170
|
/**
|
|
114
|
-
*
|
|
171
|
+
* Creates the appropriate view instance for a given item.
|
|
115
172
|
*
|
|
116
|
-
* @param parent -
|
|
117
|
-
* @param item - The item to render (group or option).
|
|
118
|
-
* @returns
|
|
173
|
+
* @param {HTMLElement} parent - Container element where the view will be mounted.
|
|
174
|
+
* @param {MixedItem} item - The item to render (group or option).
|
|
175
|
+
* @returns {GroupView | OptionView} A view instance matching the item type.
|
|
176
|
+
* @override
|
|
119
177
|
*/
|
|
120
178
|
override viewHolder(parent: HTMLElement, item: MixedItem): GroupView | OptionView {
|
|
121
179
|
if (item instanceof GroupModel) return new GroupView(parent);
|
|
@@ -123,12 +181,17 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
123
181
|
}
|
|
124
182
|
|
|
125
183
|
/**
|
|
126
|
-
* Binds a
|
|
127
|
-
* to specialized handlers.
|
|
184
|
+
* Binds a model (group or option) to its view and delegates to specialized handlers.
|
|
128
185
|
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
186
|
+
* Notes:
|
|
187
|
+
* - Assigns `item.position` in the top-level `items` list (not the `flatOptions` index).
|
|
188
|
+
* - Performs one-time listener binding guarded by `item.isInit`.
|
|
189
|
+
*
|
|
190
|
+
* @param {MixedItem} item - {@link GroupModel} or {@link OptionModel}.
|
|
191
|
+
* @param {GroupView | OptionView | null} viewer - The view instance that will render the model.
|
|
192
|
+
* @param {number} position - Position in the top-level mixed list.
|
|
193
|
+
* @returns {void}
|
|
194
|
+
* @override
|
|
132
195
|
*/
|
|
133
196
|
override onViewHolder(item: MixedItem, viewer: GroupView | OptionView | null, position: number): void {
|
|
134
197
|
item.position = position;
|
|
@@ -143,15 +206,24 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
143
206
|
}
|
|
144
207
|
|
|
145
208
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* -
|
|
150
|
-
* -
|
|
151
|
-
*
|
|
152
|
-
* @
|
|
153
|
-
*
|
|
154
|
-
*
|
|
209
|
+
* Binds / renders a group header and its option children.
|
|
210
|
+
*
|
|
211
|
+
* Responsibilities:
|
|
212
|
+
* - Set header label and click-to-toggle behavior (one-time).
|
|
213
|
+
* - Observe collapsed state:
|
|
214
|
+
* - toggles child option DOM display,
|
|
215
|
+
* - invokes {@link onCollapsedChange} hook.
|
|
216
|
+
* - Ensure each child option has a view and is bound via {@link handleOptionView}.
|
|
217
|
+
* - Sync collapsed UI and derived visibility for the group view.
|
|
218
|
+
*
|
|
219
|
+
* DOM side effects:
|
|
220
|
+
* - Adds a click listener to the group header (only once).
|
|
221
|
+
* - Updates child option view element `style.display` on collapse changes.
|
|
222
|
+
*
|
|
223
|
+
* @param {GroupModel} groupModel - Group data model.
|
|
224
|
+
* @param {GroupView} groupView - Group view instance.
|
|
225
|
+
* @param {number} position - Group index in the top-level list.
|
|
226
|
+
* @returns {void}
|
|
155
227
|
*/
|
|
156
228
|
private handleGroupView(groupModel: GroupModel, groupView: GroupView, position: number): void {
|
|
157
229
|
super.onViewHolder(groupModel, groupView, position);
|
|
@@ -196,15 +268,32 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
196
268
|
}
|
|
197
269
|
|
|
198
270
|
/**
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* -
|
|
203
|
-
* -
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
271
|
+
* Binds / renders an option row and wires selection/highlight/visibility behavior.
|
|
272
|
+
*
|
|
273
|
+
* Responsibilities:
|
|
274
|
+
* - Apply visual configuration from the model options (image sizing/position, label alignment).
|
|
275
|
+
* - Render image (src/alt) and label HTML.
|
|
276
|
+
* - Wire DOM events (one-time):
|
|
277
|
+
* - click → selection (single/multiple),
|
|
278
|
+
* - mouseenter → highlight.
|
|
279
|
+
* - Wire model hooks (one-time):
|
|
280
|
+
* - `onSelected` → `changeProp("selected")` (external semantics),
|
|
281
|
+
* - `onInternalSelected` → cache single selected + `changeProp("selected_internal")` (internal semantics),
|
|
282
|
+
* - `onVisibilityChanged` → group visibility recompute + debounced visibility stats.
|
|
283
|
+
*
|
|
284
|
+
* Selection semantics:
|
|
285
|
+
* - Multi-select: toggles `selected` for the clicked option.
|
|
286
|
+
* - Single-select: clears previous selected option (if cached) and selects the clicked one.
|
|
287
|
+
* - Both paths run `changingProp("select")` before mutating selection when not skipping events.
|
|
288
|
+
*
|
|
289
|
+
* DOM side effects:
|
|
290
|
+
* - Adds listeners to `OptionView` element only on first bind.
|
|
291
|
+
* - Updates image and label DOM each bind.
|
|
292
|
+
*
|
|
293
|
+
* @param {OptionModel} optionModel - Option data model.
|
|
294
|
+
* @param {OptionView} optionViewer - Option view instance.
|
|
295
|
+
* @param {number} position - Option index within its group list (or rendering context).
|
|
296
|
+
* @returns {void}
|
|
208
297
|
*/
|
|
209
298
|
private handleOptionView(optionModel: OptionModel, optionViewer: OptionView, position: number): void {
|
|
210
299
|
optionViewer.isMultiple = this.isMultiple;
|
|
@@ -226,13 +315,14 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
226
315
|
optionModel.view = optionViewer;
|
|
227
316
|
|
|
228
317
|
if (optionModel.hasImage) {
|
|
229
|
-
const imageTag = optionViewer.view.tags.OptionImage
|
|
318
|
+
const imageTag = optionViewer.view.tags.OptionImage;
|
|
230
319
|
if (imageTag) {
|
|
231
320
|
if (imageTag.src !== optionModel.imageSrc) imageTag.src = optionModel.imageSrc;
|
|
232
321
|
if (imageTag.alt !== optionModel.text) imageTag.alt = optionModel.text;
|
|
233
322
|
}
|
|
234
323
|
}
|
|
235
324
|
|
|
325
|
+
// Label uses HTML to support rich content; consumers must ensure the model text is safe.
|
|
236
326
|
optionViewer.view.tags.LabelContent.innerHTML = optionModel.text;
|
|
237
327
|
|
|
238
328
|
if (!optionModel.isInit) {
|
|
@@ -259,21 +349,25 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
259
349
|
this.setHighlight(this.flatOptions.indexOf(optionModel), false);
|
|
260
350
|
});
|
|
261
351
|
|
|
352
|
+
// External selection notification (user-facing semantics).
|
|
262
353
|
optionModel.onSelected((_evtToken: IEventCallback, _el: OptionModel, _selected: boolean) => {
|
|
263
354
|
this.changeProp("selected");
|
|
264
355
|
});
|
|
265
356
|
|
|
357
|
+
// Internal selection notification (non-trigger semantics).
|
|
266
358
|
optionModel.onInternalSelected((_evtToken: IEventCallback, _el: OptionModel, selected: boolean) => {
|
|
267
359
|
if (selected) this.selectedItemSingle = optionModel;
|
|
268
360
|
this.changeProp("selected_internal");
|
|
269
361
|
});
|
|
270
362
|
|
|
363
|
+
// Visibility changes affect group visibility and aggregated visibility stats.
|
|
271
364
|
optionModel.onVisibilityChanged((_evtToken: IEventCallback, model: OptionModel, _visible: boolean) => {
|
|
272
365
|
model.group?.updateVisibility();
|
|
273
366
|
this.notifyVisibilityChanged();
|
|
274
367
|
});
|
|
275
368
|
}
|
|
276
369
|
|
|
370
|
+
// Ensure single-select cache and suppress re-trigger when model is already selected.
|
|
277
371
|
if (optionModel.selected) {
|
|
278
372
|
this.selectedItemSingle = optionModel;
|
|
279
373
|
optionModel.selectedNonTrigger = true;
|
|
@@ -281,10 +375,17 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
281
375
|
}
|
|
282
376
|
|
|
283
377
|
/**
|
|
284
|
-
* Replaces items and rebuilds
|
|
285
|
-
*
|
|
378
|
+
* Replaces items and rebuilds derived structures with full change notifications.
|
|
379
|
+
*
|
|
380
|
+
* Flow:
|
|
381
|
+
* - `changingProp("items", items)` (pre-change pipeline)
|
|
382
|
+
* - assign `this.items`, rebuild `groups`/`flatOptions`
|
|
383
|
+
* - `changeProp("items", items)` (post-change pipeline)
|
|
384
|
+
* - {@link Lifecycle.update}
|
|
286
385
|
*
|
|
287
|
-
* @param items - New mixed item collection (groups/options).
|
|
386
|
+
* @param {MixedItem[]} items - New mixed item collection (groups/options).
|
|
387
|
+
* @returns {Promise<void>}
|
|
388
|
+
* @override
|
|
288
389
|
*/
|
|
289
390
|
override async setItems(items: MixedItem[]): Promise<void> {
|
|
290
391
|
await this.changingProp("items", items);
|
|
@@ -295,18 +396,23 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
295
396
|
}
|
|
296
397
|
|
|
297
398
|
/**
|
|
298
|
-
* Synchronizes items from an external source by delegating to
|
|
399
|
+
* Synchronizes items from an external source by delegating to {@link setItems}.
|
|
299
400
|
*
|
|
300
|
-
* @param items - New mixed item collection (groups/options).
|
|
401
|
+
* @param {MixedItem[]} items - New mixed item collection (groups/options).
|
|
402
|
+
* @returns {Promise<void>}
|
|
403
|
+
* @override
|
|
301
404
|
*/
|
|
302
405
|
override async syncFromSource(items: MixedItem[]): Promise<void> {
|
|
303
406
|
await this.setItems(items);
|
|
304
407
|
}
|
|
305
408
|
|
|
306
409
|
/**
|
|
307
|
-
* Updates items and rebuilds
|
|
410
|
+
* Updates items and rebuilds derived structures **without** emitting change notifications.
|
|
411
|
+
* Useful for internal reconciliation where observers should not be notified.
|
|
308
412
|
*
|
|
309
|
-
* @param items - New mixed item collection (groups/options).
|
|
413
|
+
* @param {MixedItem[]} items - New mixed item collection (groups/options).
|
|
414
|
+
* @returns {void}
|
|
415
|
+
* @override
|
|
310
416
|
*/
|
|
311
417
|
override updateData(items: MixedItem[]): void {
|
|
312
418
|
this.items = items;
|
|
@@ -315,10 +421,18 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
315
421
|
}
|
|
316
422
|
|
|
317
423
|
/**
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
* -
|
|
424
|
+
* Releases adapter resources and clears derived state.
|
|
425
|
+
*
|
|
426
|
+
* Behavior:
|
|
427
|
+
* - Clears visibility scheduler task (`sche_vis_${adapterKey}`).
|
|
428
|
+
* - Destroys all group models (which may cascade to child models/views).
|
|
429
|
+
* - Resets cached selection/highlight and subscriber lists.
|
|
430
|
+
*
|
|
431
|
+
* Idempotent:
|
|
432
|
+
* - Returns early if already in {@link LifecycleState.DESTROYED}.
|
|
433
|
+
*
|
|
434
|
+
* @returns {void}
|
|
435
|
+
* @override
|
|
322
436
|
*/
|
|
323
437
|
public override destroy(): void {
|
|
324
438
|
if (this.is(LifecycleState.DESTROYED)) {
|
|
@@ -341,27 +455,30 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
341
455
|
}
|
|
342
456
|
|
|
343
457
|
/**
|
|
344
|
-
* Returns all currently selected
|
|
458
|
+
* Returns all currently selected options from the flat list.
|
|
345
459
|
*
|
|
346
|
-
* @returns
|
|
460
|
+
* @returns {OptionModel[]} Selected options.
|
|
347
461
|
*/
|
|
348
462
|
public getSelectedItems(): OptionModel[] {
|
|
349
463
|
return this.flatOptions.filter((item) => item.selected);
|
|
350
464
|
}
|
|
351
465
|
|
|
352
466
|
/**
|
|
353
|
-
* Returns the first selected option (if any).
|
|
467
|
+
* Returns the first selected option from the flat list (if any).
|
|
468
|
+
* Primarily useful for single-select mode.
|
|
354
469
|
*
|
|
355
|
-
* @returns The first selected option
|
|
470
|
+
* @returns {OptionModel | undefined} The first selected option, or `undefined` if none.
|
|
356
471
|
*/
|
|
357
472
|
public getSelectedItem(): OptionModel | undefined {
|
|
358
473
|
return this.flatOptions.find((item) => item.selected);
|
|
359
474
|
}
|
|
360
475
|
|
|
361
476
|
/**
|
|
362
|
-
*
|
|
477
|
+
* Selects or deselects all options when in multiple selection mode.
|
|
478
|
+
* No-op if `isMultiple` is false.
|
|
363
479
|
*
|
|
364
|
-
* @param isChecked - `true` to select all; `false` to deselect all.
|
|
480
|
+
* @param {boolean} isChecked - `true` to select all; `false` to deselect all.
|
|
481
|
+
* @returns {void}
|
|
365
482
|
*/
|
|
366
483
|
public checkAll(isChecked: boolean): void {
|
|
367
484
|
if (!this.isMultiple) return;
|
|
@@ -371,16 +488,20 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
371
488
|
}
|
|
372
489
|
|
|
373
490
|
/**
|
|
374
|
-
* Subscribes
|
|
491
|
+
* Subscribes to aggregated visibility changes across all options.
|
|
492
|
+
* The callback is invoked from a debounced scheduler.
|
|
375
493
|
*
|
|
376
|
-
* @param callback - Invoked with
|
|
494
|
+
* @param {(stats: VisibilityStats) => void} callback - Invoked with `{ visibleCount, totalCount, hasVisible, isEmpty }`.
|
|
495
|
+
* @returns {void}
|
|
377
496
|
*/
|
|
378
497
|
public onVisibilityChanged(callback: (stats: VisibilityStats) => void): void {
|
|
379
498
|
this.visibilityChangedCallbacks.push(callback);
|
|
380
499
|
}
|
|
381
500
|
|
|
382
501
|
/**
|
|
383
|
-
* Schedules a visibility statistics recomputation and
|
|
502
|
+
* Schedules a debounced visibility statistics recomputation and subscriber notification.
|
|
503
|
+
*
|
|
504
|
+
* @returns {void}
|
|
384
505
|
*/
|
|
385
506
|
private notifyVisibilityChanged(): void {
|
|
386
507
|
Libs.callbackScheduler.run(`sche_vis_${this.adapterKey}`);
|
|
@@ -389,7 +510,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
389
510
|
/**
|
|
390
511
|
* Computes and returns current visibility statistics for options.
|
|
391
512
|
*
|
|
392
|
-
* @returns Aggregated stats: `{ visibleCount, totalCount, hasVisible, isEmpty }`.
|
|
513
|
+
* @returns {VisibilityStats} Aggregated stats: `{ visibleCount, totalCount, hasVisible, isEmpty }`.
|
|
393
514
|
*/
|
|
394
515
|
public getVisibilityStats(): VisibilityStats {
|
|
395
516
|
const visibleCount = this.flatOptions.filter((item) => item.visible).length;
|
|
@@ -404,17 +525,23 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
404
525
|
}
|
|
405
526
|
|
|
406
527
|
/**
|
|
407
|
-
* Resets
|
|
528
|
+
* Resets highlight navigation to the first visible option (starting from index 0).
|
|
529
|
+
*
|
|
530
|
+
* @returns {void}
|
|
408
531
|
*/
|
|
409
532
|
public resetHighlight(): void {
|
|
410
533
|
this.setHighlight(0);
|
|
411
534
|
}
|
|
412
535
|
|
|
413
536
|
/**
|
|
414
|
-
* Moves
|
|
537
|
+
* Moves highlight among **visible** options and optionally scrolls the new target into view.
|
|
538
|
+
*
|
|
539
|
+
* - Wraps around at both ends (circular navigation).
|
|
540
|
+
* - Uses the current highlight (flat index) as the starting point.
|
|
415
541
|
*
|
|
416
|
-
* @param direction -
|
|
417
|
-
* @param isScrollToView - Whether to scroll the highlighted item into view.
|
|
542
|
+
* @param {number} direction - `+1` to move forward; `-1` to move backward.
|
|
543
|
+
* @param {boolean} [isScrollToView=true] - Whether to scroll the highlighted item into view.
|
|
544
|
+
* @returns {void}
|
|
418
545
|
*/
|
|
419
546
|
public navigate(direction: number, isScrollToView: boolean = true): void {
|
|
420
547
|
const visibleOptions = this.flatOptions.filter((opt) => opt.visible);
|
|
@@ -436,8 +563,18 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
436
563
|
}
|
|
437
564
|
|
|
438
565
|
/**
|
|
439
|
-
*
|
|
440
|
-
*
|
|
566
|
+
* Programmatically selects (clicks) the currently highlighted option if it is visible.
|
|
567
|
+
*
|
|
568
|
+
* DOM side effects:
|
|
569
|
+
* - Calls `HTMLElement.click()` on the rendered option view element.
|
|
570
|
+
*
|
|
571
|
+
* No-op if:
|
|
572
|
+
* - No highlight is set,
|
|
573
|
+
* - Highlighted item does not exist,
|
|
574
|
+
* - Highlighted item is not visible,
|
|
575
|
+
* - View element is not available (e.g., not rendered).
|
|
576
|
+
*
|
|
577
|
+
* @returns {void}
|
|
441
578
|
*/
|
|
442
579
|
public selectHighlighted(): void {
|
|
443
580
|
if (this.currentHighlightIndex > -1 && this.flatOptions[this.currentHighlightIndex]) {
|
|
@@ -450,11 +587,20 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
450
587
|
}
|
|
451
588
|
|
|
452
589
|
/**
|
|
453
|
-
* Highlights a target option (by flat index or model reference),
|
|
454
|
-
*
|
|
590
|
+
* Highlights a target option (by flat index or model reference), skipping invisible items.
|
|
591
|
+
* Optionally scrolls the highlighted item into view.
|
|
592
|
+
*
|
|
593
|
+
* Behavior:
|
|
594
|
+
* - Clears previous highlight (if any) by toggling `OptionModel.highlighted = false`.
|
|
595
|
+
* - Starting from the resolved index, finds the first visible option and highlights it.
|
|
596
|
+
* - If scrolling is enabled:
|
|
597
|
+
* - scrolls the DOM element when available, otherwise
|
|
598
|
+
* - asks the recycler to render the item and scroll into view (virtualized lists).
|
|
599
|
+
* - Invokes {@link onHighlightChange} hook after applying highlight.
|
|
455
600
|
*
|
|
456
|
-
* @param target - Flat index or
|
|
457
|
-
* @param isScrollToView - Whether to scroll the highlighted item into view.
|
|
601
|
+
* @param {number | OptionModel} target - Flat index or option model to highlight.
|
|
602
|
+
* @param {boolean} [isScrollToView=true] - Whether to scroll the highlighted item into view.
|
|
603
|
+
* @returns {void}
|
|
458
604
|
*/
|
|
459
605
|
public setHighlight(target: number | OptionModel, isScrollToView: boolean = true): void {
|
|
460
606
|
let index = 0;
|
|
@@ -495,20 +641,30 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
|
|
|
495
641
|
}
|
|
496
642
|
|
|
497
643
|
/**
|
|
498
|
-
* Hook
|
|
499
|
-
* Override to handle UI side effects (e.g., ARIA announcement, focus sync).
|
|
644
|
+
* Hook called whenever highlight changes.
|
|
500
645
|
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
646
|
+
* Intended for UI side effects that do not belong in core adapter logic, e.g.:
|
|
647
|
+
* - roving tabindex / focus synchronization,
|
|
648
|
+
* - ARIA live region announcements,
|
|
649
|
+
* - analytics/navigation instrumentation.
|
|
650
|
+
*
|
|
651
|
+
* @param {number} index - Flat index of the newly highlighted option.
|
|
652
|
+
* @param {string} [id] - Optional DOM id of the highlighted view element (when available).
|
|
653
|
+
* @returns {void}
|
|
503
654
|
*/
|
|
504
655
|
public onHighlightChange(index: number, id?: string): void { }
|
|
505
656
|
|
|
506
657
|
/**
|
|
507
|
-
* Hook
|
|
508
|
-
*
|
|
658
|
+
* Hook called whenever a group's collapsed state changes.
|
|
659
|
+
*
|
|
660
|
+
* Intended for integration side effects, e.g.:
|
|
661
|
+
* - layout recalculation,
|
|
662
|
+
* - popup resize,
|
|
663
|
+
* - analytics.
|
|
509
664
|
*
|
|
510
|
-
* @param model - The group whose collapsed state changed.
|
|
511
|
-
* @param collapsed - New collapsed state.
|
|
665
|
+
* @param {GroupModel} model - The group whose collapsed state changed.
|
|
666
|
+
* @param {boolean} collapsed - New collapsed state.
|
|
667
|
+
* @returns {void}
|
|
512
668
|
*/
|
|
513
669
|
public onCollapsedChange(model: GroupModel, collapsed: boolean): void { }
|
|
514
670
|
}
|