selective-ui 1.2.0 → 1.2.2
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 +3 -1
- package/dist/selective-ui.css.map +1 -1
- package/dist/selective-ui.esm.js +487 -436
- 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 +488 -437
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/css/components/popup.css +3 -1
- package/src/ts/adapter/mixed-adapter.ts +56 -50
- package/src/ts/components/accessorybox.ts +49 -21
- package/src/ts/components/directive.ts +3 -3
- package/src/ts/components/empty-state.ts +7 -7
- package/src/ts/components/loading-state.ts +7 -7
- package/src/ts/components/option-handle.ts +17 -17
- package/src/ts/components/placeholder.ts +12 -12
- package/src/ts/components/popup.ts +93 -108
- package/src/ts/components/searchbox.ts +14 -14
- package/src/ts/components/selectbox.ts +41 -31
- package/src/ts/core/base/adapter.ts +12 -12
- package/src/ts/core/base/model.ts +12 -13
- package/src/ts/core/base/recyclerview.ts +7 -7
- package/src/ts/core/base/view.ts +6 -6
- package/src/ts/core/base/virtual-recyclerview.ts +51 -50
- package/src/ts/core/model-manager.ts +53 -53
- package/src/ts/core/search-controller.ts +70 -73
- package/src/ts/models/group-model.ts +21 -21
- package/src/ts/models/option-model.ts +50 -31
- package/src/ts/services/dataset-observer.ts +17 -17
- package/src/ts/services/ea-observer.ts +21 -21
- package/src/ts/services/effector.ts +30 -30
- package/src/ts/services/refresher.ts +1 -1
- package/src/ts/services/resize-observer.ts +29 -29
- package/src/ts/services/select-observer.ts +29 -29
- package/src/ts/types/components/popup.type.ts +15 -0
- package/src/ts/types/utils/istorage.type.ts +1 -1
- package/src/ts/utils/callback-scheduler.ts +4 -4
- package/src/ts/utils/ievents.ts +4 -4
- package/src/ts/utils/istorage.ts +5 -5
- package/src/ts/utils/libs.ts +38 -29
- package/src/ts/utils/selective.ts +11 -11
- package/src/ts/views/group-view.ts +7 -7
- package/src/ts/views/option-view.ts +51 -51
|
@@ -9,19 +9,19 @@ import { Libs } from "../utils/libs";
|
|
|
9
9
|
import { ModelManager } from "./model-manager";
|
|
10
10
|
|
|
11
11
|
export class SearchController {
|
|
12
|
-
private
|
|
12
|
+
private select: HTMLSelectElement;
|
|
13
13
|
|
|
14
|
-
private
|
|
14
|
+
private modelManager: ModelManager<MixedItem, any>;
|
|
15
15
|
|
|
16
|
-
private
|
|
16
|
+
private ajaxConfig: AjaxConfig | null = null;
|
|
17
17
|
|
|
18
|
-
private
|
|
18
|
+
private abortController: AbortController | null = null;
|
|
19
19
|
|
|
20
|
-
private
|
|
20
|
+
private popup: Popup | null = null;
|
|
21
21
|
|
|
22
|
-
private
|
|
22
|
+
private selectBox: SelectBox = null;
|
|
23
23
|
|
|
24
|
-
private
|
|
24
|
+
private paginationState: PaginationState = {
|
|
25
25
|
currentPage: 0,
|
|
26
26
|
totalPages: 1,
|
|
27
27
|
hasMore: false,
|
|
@@ -38,10 +38,10 @@ export class SearchController {
|
|
|
38
38
|
* @param {ModelManager<MixedItem, any>} modelManager - Manager responsible for models and rendering updates.
|
|
39
39
|
* @param {SelectBox} selectBox - SelectBox handle.
|
|
40
40
|
*/
|
|
41
|
-
constructor(selectElement: HTMLSelectElement, modelManager: ModelManager<MixedItem, any>, selectBox: SelectBox) {
|
|
42
|
-
this.
|
|
43
|
-
this.
|
|
44
|
-
this.
|
|
41
|
+
public constructor(selectElement: HTMLSelectElement, modelManager: ModelManager<MixedItem, any>, selectBox: SelectBox) {
|
|
42
|
+
this.select = selectElement;
|
|
43
|
+
this.modelManager = modelManager;
|
|
44
|
+
this.selectBox = selectBox;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/**
|
|
@@ -49,8 +49,8 @@ export class SearchController {
|
|
|
49
49
|
*
|
|
50
50
|
* @returns {boolean} - True if AJAX config is present; false otherwise.
|
|
51
51
|
*/
|
|
52
|
-
isAjax(): boolean {
|
|
53
|
-
return !!this.
|
|
52
|
+
public isAjax(): boolean {
|
|
53
|
+
return !!this.ajaxConfig;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
/**
|
|
@@ -59,7 +59,7 @@ export class SearchController {
|
|
|
59
59
|
* @returns {Promise<{success: boolean, items: Array, message?: string}>}
|
|
60
60
|
*/
|
|
61
61
|
async loadByValues(values: string | string[]): Promise<{ success: boolean; items: NormalizedAjaxItem[]; message?: string }> {
|
|
62
|
-
if (!this.
|
|
62
|
+
if (!this.ajaxConfig) {
|
|
63
63
|
return { success: false, items: [], message: "Ajax not configured" };
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -67,7 +67,7 @@ export class SearchController {
|
|
|
67
67
|
if (valuesArray.length === 0) return { success: true, items: [] };
|
|
68
68
|
|
|
69
69
|
try {
|
|
70
|
-
const cfg = this.
|
|
70
|
+
const cfg = this.ajaxConfig;
|
|
71
71
|
|
|
72
72
|
let payload: Record<string, any>;
|
|
73
73
|
if (typeof cfg.dataByValues === "function") {
|
|
@@ -76,7 +76,7 @@ export class SearchController {
|
|
|
76
76
|
payload = {
|
|
77
77
|
values: valuesArray.join(","),
|
|
78
78
|
load_by_values: "1",
|
|
79
|
-
...(typeof cfg.data === "function" ? cfg.data.bind(this.
|
|
79
|
+
...(typeof cfg.data === "function" ? cfg.data.bind(this.selectBox.Selective.find(this.selectBox.container.targetElement))("", 0) : cfg.data ?? {}),
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -98,7 +98,7 @@ export class SearchController {
|
|
|
98
98
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
|
99
99
|
|
|
100
100
|
const data = await response.json();
|
|
101
|
-
const result = this.
|
|
101
|
+
const result = this.parseResponse(data);
|
|
102
102
|
|
|
103
103
|
return { success: true, items: result.items };
|
|
104
104
|
} catch (error: any) {
|
|
@@ -112,8 +112,8 @@ export class SearchController {
|
|
|
112
112
|
* @param {string[]} values - Values to check
|
|
113
113
|
* @returns {{existing: string[], missing: string[]}}
|
|
114
114
|
*/
|
|
115
|
-
checkMissingValues(values: string[]): { existing: string[]; missing: string[] } {
|
|
116
|
-
const allOptions = Array.from(this.
|
|
115
|
+
public checkMissingValues(values: string[]): { existing: string[]; missing: string[] } {
|
|
116
|
+
const allOptions = Array.from(this.select.options);
|
|
117
117
|
const existingValues = allOptions.map((opt) => opt.value);
|
|
118
118
|
|
|
119
119
|
const existing = values.filter((v) => existingValues.includes(v));
|
|
@@ -127,8 +127,8 @@ export class SearchController {
|
|
|
127
127
|
*
|
|
128
128
|
* @param {object} config - AJAX configuration object (e.g., endpoint, headers, query params).
|
|
129
129
|
*/
|
|
130
|
-
setAjax(config: AjaxConfig | null): void {
|
|
131
|
-
this.
|
|
130
|
+
public setAjax(config: AjaxConfig | null): void {
|
|
131
|
+
this.ajaxConfig = config;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
/**
|
|
@@ -136,39 +136,39 @@ export class SearchController {
|
|
|
136
136
|
*
|
|
137
137
|
* @param {Popup} popupInstance - The popup used to display search results and loading state.
|
|
138
138
|
*/
|
|
139
|
-
setPopup(popupInstance: Popup): void {
|
|
140
|
-
this.
|
|
139
|
+
public setPopup(popupInstance: Popup): void {
|
|
140
|
+
this.popup = popupInstance;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
/**
|
|
144
144
|
* Returns a shallow copy of the current pagination state used for search/infinite scroll.
|
|
145
145
|
*/
|
|
146
|
-
getPaginationState(): PaginationState {
|
|
147
|
-
return { ...this.
|
|
146
|
+
public getPaginationState(): PaginationState {
|
|
147
|
+
return { ...this.paginationState };
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
/**
|
|
151
151
|
* Resets pagination counters while preserving whether pagination is enabled.
|
|
152
152
|
* Clears page, totals, loading flags, and current keyword.
|
|
153
153
|
*/
|
|
154
|
-
resetPagination(): void {
|
|
155
|
-
this.
|
|
154
|
+
public resetPagination(): void {
|
|
155
|
+
this.paginationState = {
|
|
156
156
|
currentPage: 0,
|
|
157
157
|
totalPages: 1,
|
|
158
158
|
hasMore: false,
|
|
159
159
|
isLoading: false,
|
|
160
160
|
currentKeyword: "",
|
|
161
|
-
isPaginationEnabled: this.
|
|
161
|
+
isPaginationEnabled: this.paginationState.isPaginationEnabled,
|
|
162
162
|
};
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
166
|
* Clears the current keyword and makes all options visible (local reset).
|
|
167
167
|
*/
|
|
168
|
-
clear(): void {
|
|
169
|
-
this.
|
|
168
|
+
public clear(): void {
|
|
169
|
+
this.paginationState.currentKeyword = "";
|
|
170
170
|
|
|
171
|
-
const { modelList } = this.
|
|
171
|
+
const { modelList } = this.modelManager.getResources();
|
|
172
172
|
const flatOptions: OptionModel[] = [];
|
|
173
173
|
|
|
174
174
|
for (const m of modelList as MixedItem[]) {
|
|
@@ -184,22 +184,22 @@ export class SearchController {
|
|
|
184
184
|
/**
|
|
185
185
|
* Performs a search with either AJAX or local filtering depending on configuration.
|
|
186
186
|
*/
|
|
187
|
-
async search(keyword: string, append: boolean = false): Promise<any> {
|
|
188
|
-
if (this.
|
|
187
|
+
public async search(keyword: string, append: boolean = false): Promise<any> {
|
|
188
|
+
if (this.ajaxConfig) return this._ajaxSearch(keyword, append);
|
|
189
189
|
return this._localSearch(keyword);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
/**
|
|
193
193
|
* Loads the next page for AJAX pagination if enabled and not already loading.
|
|
194
194
|
*/
|
|
195
|
-
async loadMore(): Promise<any> {
|
|
196
|
-
if (!this.
|
|
197
|
-
if (this.
|
|
198
|
-
if (!this.
|
|
199
|
-
if (!this.
|
|
200
|
-
|
|
201
|
-
this.
|
|
202
|
-
return this._ajaxSearch(this.
|
|
195
|
+
public async loadMore(): Promise<any> {
|
|
196
|
+
if (!this.ajaxConfig) return { success: false, message: "Ajax not enabled" };
|
|
197
|
+
if (this.paginationState.isLoading) return { success: false, message: "Already loading" };
|
|
198
|
+
if (!this.paginationState.isPaginationEnabled) return { success: false, message: "Pagination not enabled" };
|
|
199
|
+
if (!this.paginationState.hasMore) return { success: false, message: "No more data" };
|
|
200
|
+
|
|
201
|
+
this.paginationState.currentPage++;
|
|
202
|
+
return this._ajaxSearch(this.paginationState.currentKeyword, true);
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
/**
|
|
@@ -207,12 +207,12 @@ export class SearchController {
|
|
|
207
207
|
* and toggling each option's visibility based on text match. Returns summary flags.
|
|
208
208
|
*/
|
|
209
209
|
private async _localSearch(keyword: string): Promise<{ success: boolean; hasResults: boolean; isEmpty: boolean }> {
|
|
210
|
-
if (this.compareSearchTrigger(keyword)) this.
|
|
210
|
+
if (this.compareSearchTrigger(keyword)) this.paginationState.currentKeyword = keyword;
|
|
211
211
|
|
|
212
212
|
const lower = String(keyword ?? "").toLowerCase();
|
|
213
213
|
const lowerNA = Libs.string2normalize(lower);
|
|
214
214
|
|
|
215
|
-
const { modelList } = this.
|
|
215
|
+
const { modelList } = this.modelManager.getResources();
|
|
216
216
|
|
|
217
217
|
const flatOptions: OptionModel[] = [];
|
|
218
218
|
for (const m of modelList as MixedItem[]) {
|
|
@@ -223,10 +223,7 @@ export class SearchController {
|
|
|
223
223
|
let hasVisibleItems = false;
|
|
224
224
|
|
|
225
225
|
flatOptions.forEach((opt) => {
|
|
226
|
-
const
|
|
227
|
-
const textNA = Libs.string2normalize(text);
|
|
228
|
-
|
|
229
|
-
const isVisible = lower === "" || text.includes(lower) || textNA.includes(lowerNA);
|
|
226
|
+
const isVisible = lower === "" || opt.textToFind.includes(lowerNA);
|
|
230
227
|
|
|
231
228
|
opt.visible = isVisible;
|
|
232
229
|
if (isVisible) hasVisibleItems = true;
|
|
@@ -243,36 +240,36 @@ export class SearchController {
|
|
|
243
240
|
* Checks whether the provided keyword differs from the current one,
|
|
244
241
|
* to determine if a new search should be triggered.
|
|
245
242
|
*/
|
|
246
|
-
compareSearchTrigger(keyword: string): boolean {
|
|
247
|
-
return keyword !== this.
|
|
243
|
+
public compareSearchTrigger(keyword: string): boolean {
|
|
244
|
+
return keyword !== this.paginationState.currentKeyword;
|
|
248
245
|
}
|
|
249
246
|
|
|
250
247
|
/**
|
|
251
248
|
* Executes an AJAX-based search with optional appending.
|
|
252
249
|
*/
|
|
253
250
|
private async _ajaxSearch(keyword: string, append: boolean = false): Promise<any> {
|
|
254
|
-
const cfg = this.
|
|
251
|
+
const cfg = this.ajaxConfig!;
|
|
255
252
|
if (this.compareSearchTrigger(keyword)) {
|
|
256
253
|
this.resetPagination();
|
|
257
|
-
this.
|
|
254
|
+
this.paginationState.currentKeyword = keyword;
|
|
258
255
|
append = false;
|
|
259
256
|
}
|
|
260
257
|
|
|
261
|
-
this.
|
|
262
|
-
this.
|
|
258
|
+
this.paginationState.isLoading = true;
|
|
259
|
+
this.popup?.showLoading();
|
|
263
260
|
|
|
264
|
-
this.
|
|
265
|
-
this.
|
|
261
|
+
this.abortController?.abort();
|
|
262
|
+
this.abortController = new AbortController();
|
|
266
263
|
|
|
267
|
-
const page = this.
|
|
264
|
+
const page = this.paginationState.currentPage;
|
|
268
265
|
|
|
269
|
-
const selectedValues = Array.from(this.
|
|
266
|
+
const selectedValues = Array.from(this.select.selectedOptions)
|
|
270
267
|
.map((opt) => opt.value)
|
|
271
268
|
.join(",");
|
|
272
269
|
|
|
273
270
|
let payload: Record<string, any>;
|
|
274
271
|
if (typeof cfg.data === "function") {
|
|
275
|
-
payload = cfg.data.bind(this.
|
|
272
|
+
payload = cfg.data.bind(this.selectBox.Selective.find(this.selectBox.container.targetElement))(keyword, page);
|
|
276
273
|
if (payload && typeof payload.selectedValue === "undefined") payload.selectedValue = selectedValues;
|
|
277
274
|
} else {
|
|
278
275
|
payload = { search: keyword, page, selectedValue: selectedValues, ...(cfg.data ?? {}) };
|
|
@@ -288,29 +285,29 @@ export class SearchController {
|
|
|
288
285
|
method: "POST",
|
|
289
286
|
body: formData,
|
|
290
287
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
291
|
-
signal: this.
|
|
288
|
+
signal: this.abortController.signal,
|
|
292
289
|
});
|
|
293
290
|
} else {
|
|
294
291
|
const params = new URLSearchParams(payload).toString();
|
|
295
|
-
response = await fetch(`${cfg.url}?${params}`, { signal: this.
|
|
292
|
+
response = await fetch(`${cfg.url}?${params}`, { signal: this.abortController.signal });
|
|
296
293
|
}
|
|
297
294
|
|
|
298
295
|
const data = await response.json();
|
|
299
|
-
const result = this.
|
|
296
|
+
const result = this.parseResponse(data);
|
|
300
297
|
|
|
301
298
|
if (result.hasPagination) {
|
|
302
|
-
this.
|
|
303
|
-
this.
|
|
304
|
-
this.
|
|
305
|
-
this.
|
|
299
|
+
this.paginationState.isPaginationEnabled = true;
|
|
300
|
+
this.paginationState.currentPage = result.page;
|
|
301
|
+
this.paginationState.totalPages = result.totalPages;
|
|
302
|
+
this.paginationState.hasMore = result.hasMore;
|
|
306
303
|
} else {
|
|
307
|
-
this.
|
|
304
|
+
this.paginationState.isPaginationEnabled = false;
|
|
308
305
|
}
|
|
309
306
|
|
|
310
307
|
this.applyAjaxResult(result.items, !!cfg.keepSelected, append);
|
|
311
308
|
|
|
312
|
-
this.
|
|
313
|
-
this.
|
|
309
|
+
this.paginationState.isLoading = false;
|
|
310
|
+
this.popup?.hideLoading();
|
|
314
311
|
|
|
315
312
|
return {
|
|
316
313
|
success: true,
|
|
@@ -322,8 +319,8 @@ export class SearchController {
|
|
|
322
319
|
totalPages: result.totalPages,
|
|
323
320
|
};
|
|
324
321
|
} catch (error: any) {
|
|
325
|
-
this.
|
|
326
|
-
this.
|
|
322
|
+
this.paginationState.isLoading = false;
|
|
323
|
+
this.popup?.hideLoading();
|
|
327
324
|
|
|
328
325
|
if (error?.name === "AbortError") return { success: false, message: "Request aborted" };
|
|
329
326
|
|
|
@@ -335,7 +332,7 @@ export class SearchController {
|
|
|
335
332
|
/**
|
|
336
333
|
* Parses various server response shapes into a normalized structure for options and groups.
|
|
337
334
|
*/
|
|
338
|
-
private
|
|
335
|
+
private parseResponse(data: any): ParseResponseResult {
|
|
339
336
|
let items: any[] = [];
|
|
340
337
|
let hasPagination = false;
|
|
341
338
|
let page = 0;
|
|
@@ -404,8 +401,8 @@ export class SearchController {
|
|
|
404
401
|
/**
|
|
405
402
|
* Applies normalized AJAX results to the underlying <select> element.
|
|
406
403
|
*/
|
|
407
|
-
applyAjaxResult(items: NormalizedAjaxItem[], keepSelected: boolean, append: boolean = false): void {
|
|
408
|
-
const select = this.
|
|
404
|
+
public applyAjaxResult(items: NormalizedAjaxItem[], keepSelected: boolean, append: boolean = false): void {
|
|
405
|
+
const select = this.select;
|
|
409
406
|
|
|
410
407
|
let oldSelected: string[] = [];
|
|
411
408
|
if (keepSelected) oldSelected = Array.from(select.selectedOptions).map((o) => o.value);
|
|
@@ -5,28 +5,28 @@ import type { GroupViewTags } from "../types/views/view.group.type";
|
|
|
5
5
|
import { GroupView } from "../views/group-view";
|
|
6
6
|
import { OptionModel } from "./option-model";
|
|
7
7
|
import type { IEventCallback } from "../types/utils/ievents.type";
|
|
8
|
-
import {
|
|
8
|
+
import { SelectiveOptions } from "../types/utils/selective.type";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @extends {Model<HTMLOptGroupElement, GroupViewTags, GroupView>}
|
|
12
12
|
*/
|
|
13
|
-
export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupView,
|
|
14
|
-
label = "";
|
|
13
|
+
export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupView, SelectiveOptions> {
|
|
14
|
+
public label = "";
|
|
15
15
|
|
|
16
|
-
items: OptionModel[] = [];
|
|
16
|
+
public items: OptionModel[] = [];
|
|
17
17
|
|
|
18
|
-
collapsed = false;
|
|
18
|
+
public collapsed = false;
|
|
19
19
|
|
|
20
|
-
private
|
|
20
|
+
private privOnCollapsedChanged: Array<(evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void> = [];
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Initializes a group model with options and an optional <optgroup> target.
|
|
24
24
|
* Reads the label and collapsed state from the target element's attributes/dataset.
|
|
25
25
|
*
|
|
26
|
-
* @param {
|
|
26
|
+
* @param {SelectiveOptions} options - Configuration for the model.
|
|
27
27
|
* @param {HTMLOptGroupElement} [targetElement] - The source <optgroup> element.
|
|
28
28
|
*/
|
|
29
|
-
constructor(options:
|
|
29
|
+
public constructor(options: SelectiveOptions, targetElement?: HTMLOptGroupElement) {
|
|
30
30
|
super(options, targetElement ?? null, null);
|
|
31
31
|
|
|
32
32
|
if (targetElement) {
|
|
@@ -40,7 +40,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
40
40
|
*
|
|
41
41
|
* @type {string[]}
|
|
42
42
|
*/
|
|
43
|
-
get value(): string[] {
|
|
43
|
+
public get value(): string[] {
|
|
44
44
|
return this.items.map((item) => item.value);
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -49,7 +49,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
49
49
|
*
|
|
50
50
|
* @type {OptionModel[]}
|
|
51
51
|
*/
|
|
52
|
-
get selectedItems(): OptionModel[] {
|
|
52
|
+
public get selectedItems(): OptionModel[] {
|
|
53
53
|
return this.items.filter((item) => item.selected);
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -58,7 +58,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
58
58
|
*
|
|
59
59
|
* @type {OptionModel[]}
|
|
60
60
|
*/
|
|
61
|
-
get visibleItems(): OptionModel[] {
|
|
61
|
+
public get visibleItems(): OptionModel[] {
|
|
62
62
|
return this.items.filter((item) => item.visible);
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -67,7 +67,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
67
67
|
*
|
|
68
68
|
* @type {boolean}
|
|
69
69
|
*/
|
|
70
|
-
get hasVisibleItems(): boolean {
|
|
70
|
+
public get hasVisibleItems(): boolean {
|
|
71
71
|
return this.visibleItems.length > 0;
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -76,7 +76,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
76
76
|
*
|
|
77
77
|
* @param {HTMLOptGroupElement} targetElement - The updated <optgroup> element.
|
|
78
78
|
*/
|
|
79
|
-
update(targetElement: HTMLOptGroupElement): void {
|
|
79
|
+
public update(targetElement: HTMLOptGroupElement): void {
|
|
80
80
|
this.label = targetElement.label;
|
|
81
81
|
this.view?.updateLabel(this.label);
|
|
82
82
|
}
|
|
@@ -85,7 +85,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
85
85
|
* Hook invoked when the target element reference changes.
|
|
86
86
|
* Updates the view's label and collapsed state to keep UI in sync.
|
|
87
87
|
*/
|
|
88
|
-
onTargetChanged(): void {
|
|
88
|
+
public onTargetChanged(): void {
|
|
89
89
|
if (this.view) {
|
|
90
90
|
this.view.updateLabel(this.label);
|
|
91
91
|
this.view.setCollapsed(this.collapsed);
|
|
@@ -97,18 +97,18 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
97
97
|
*
|
|
98
98
|
* @param {(evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void} callback - Listener for collapse changes.
|
|
99
99
|
*/
|
|
100
|
-
onCollapsedChanged(callback: (evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void): void {
|
|
101
|
-
this.
|
|
100
|
+
public onCollapsedChanged(callback: (evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void): void {
|
|
101
|
+
this.privOnCollapsedChanged.push(callback);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
/**
|
|
105
105
|
* Toggles the group's collapsed state, updates the view, and notifies registered listeners.
|
|
106
106
|
*/
|
|
107
|
-
toggleCollapse(): void {
|
|
107
|
+
public toggleCollapse(): void {
|
|
108
108
|
this.collapsed = !this.collapsed;
|
|
109
109
|
this.view?.setCollapsed(this.collapsed);
|
|
110
110
|
|
|
111
|
-
iEvents.callEvent<[GroupModel, boolean]>([this, this.collapsed], ...this.
|
|
111
|
+
iEvents.callEvent<[GroupModel, boolean]>([this, this.collapsed], ...this.privOnCollapsedChanged);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
/**
|
|
@@ -116,7 +116,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
116
116
|
*
|
|
117
117
|
* @param {OptionModel} optionModel - The option to add.
|
|
118
118
|
*/
|
|
119
|
-
addItem(optionModel: OptionModel): void {
|
|
119
|
+
public addItem(optionModel: OptionModel): void {
|
|
120
120
|
this.items.push(optionModel);
|
|
121
121
|
optionModel.group = this;
|
|
122
122
|
}
|
|
@@ -126,7 +126,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
126
126
|
*
|
|
127
127
|
* @param {OptionModel} optionModel - The option to remove.
|
|
128
128
|
*/
|
|
129
|
-
removeItem(optionModel: OptionModel): void {
|
|
129
|
+
public removeItem(optionModel: OptionModel): void {
|
|
130
130
|
const index = this.items.indexOf(optionModel);
|
|
131
131
|
if (index > -1) {
|
|
132
132
|
this.items.splice(index, 1);
|
|
@@ -138,7 +138,7 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
|
|
|
138
138
|
* Updates the group's visibility in the view, typically based on children visibility.
|
|
139
139
|
* No-ops if the view is not initialized.
|
|
140
140
|
*/
|
|
141
|
-
updateVisibility(): void {
|
|
141
|
+
public updateVisibility(): void {
|
|
142
142
|
this.view?.updateVisibility();
|
|
143
143
|
}
|
|
144
144
|
}
|