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.
Files changed (48) hide show
  1. package/dist/selective-ui.css +3 -1
  2. package/dist/selective-ui.css.map +1 -1
  3. package/dist/selective-ui.esm.js +487 -436
  4. package/dist/selective-ui.esm.js.map +1 -1
  5. package/dist/selective-ui.esm.min.js +2 -2
  6. package/dist/selective-ui.esm.min.js.br +0 -0
  7. package/dist/selective-ui.min.css +1 -1
  8. package/dist/selective-ui.min.css.br +0 -0
  9. package/dist/selective-ui.min.js +2 -2
  10. package/dist/selective-ui.min.js.br +0 -0
  11. package/dist/selective-ui.umd.js +488 -437
  12. package/dist/selective-ui.umd.js.map +1 -1
  13. package/package.json +1 -1
  14. package/src/css/components/popup.css +3 -1
  15. package/src/ts/adapter/mixed-adapter.ts +56 -50
  16. package/src/ts/components/accessorybox.ts +49 -21
  17. package/src/ts/components/directive.ts +3 -3
  18. package/src/ts/components/empty-state.ts +7 -7
  19. package/src/ts/components/loading-state.ts +7 -7
  20. package/src/ts/components/option-handle.ts +17 -17
  21. package/src/ts/components/placeholder.ts +12 -12
  22. package/src/ts/components/popup.ts +93 -108
  23. package/src/ts/components/searchbox.ts +14 -14
  24. package/src/ts/components/selectbox.ts +41 -31
  25. package/src/ts/core/base/adapter.ts +12 -12
  26. package/src/ts/core/base/model.ts +12 -13
  27. package/src/ts/core/base/recyclerview.ts +7 -7
  28. package/src/ts/core/base/view.ts +6 -6
  29. package/src/ts/core/base/virtual-recyclerview.ts +51 -50
  30. package/src/ts/core/model-manager.ts +53 -53
  31. package/src/ts/core/search-controller.ts +70 -73
  32. package/src/ts/models/group-model.ts +21 -21
  33. package/src/ts/models/option-model.ts +50 -31
  34. package/src/ts/services/dataset-observer.ts +17 -17
  35. package/src/ts/services/ea-observer.ts +21 -21
  36. package/src/ts/services/effector.ts +30 -30
  37. package/src/ts/services/refresher.ts +1 -1
  38. package/src/ts/services/resize-observer.ts +29 -29
  39. package/src/ts/services/select-observer.ts +29 -29
  40. package/src/ts/types/components/popup.type.ts +15 -0
  41. package/src/ts/types/utils/istorage.type.ts +1 -1
  42. package/src/ts/utils/callback-scheduler.ts +4 -4
  43. package/src/ts/utils/ievents.ts +4 -4
  44. package/src/ts/utils/istorage.ts +5 -5
  45. package/src/ts/utils/libs.ts +38 -29
  46. package/src/ts/utils/selective.ts +11 -11
  47. package/src/ts/views/group-view.ts +7 -7
  48. 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 _select: HTMLSelectElement;
12
+ private select: HTMLSelectElement;
13
13
 
14
- private _modelManager: ModelManager<MixedItem, any>;
14
+ private modelManager: ModelManager<MixedItem, any>;
15
15
 
16
- private _ajaxConfig: AjaxConfig | null = null;
16
+ private ajaxConfig: AjaxConfig | null = null;
17
17
 
18
- private _abortController: AbortController | null = null;
18
+ private abortController: AbortController | null = null;
19
19
 
20
- private _popup: Popup | null = null;
20
+ private popup: Popup | null = null;
21
21
 
22
- private _selectBox: SelectBox = null;
22
+ private selectBox: SelectBox = null;
23
23
 
24
- private _paginationState: PaginationState = {
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._select = selectElement;
43
- this._modelManager = modelManager;
44
- this._selectBox = selectBox;
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._ajaxConfig;
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._ajaxConfig) {
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._ajaxConfig;
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._selectBox.Selective.find(this._selectBox.container.targetElement))("", 0) : cfg.data ?? {}),
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._parseResponse(data);
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._select.options);
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._ajaxConfig = config;
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._popup = popupInstance;
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._paginationState };
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._paginationState = {
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._paginationState.isPaginationEnabled,
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._paginationState.currentKeyword = "";
168
+ public clear(): void {
169
+ this.paginationState.currentKeyword = "";
170
170
 
171
- const { modelList } = this._modelManager.getResources();
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._ajaxConfig) return this._ajaxSearch(keyword, append);
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._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);
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._paginationState.currentKeyword = keyword;
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._modelManager.getResources();
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 text = String(opt.textContent ?? opt.text ?? "").toLowerCase();
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._paginationState.currentKeyword;
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._ajaxConfig!;
251
+ const cfg = this.ajaxConfig!;
255
252
  if (this.compareSearchTrigger(keyword)) {
256
253
  this.resetPagination();
257
- this._paginationState.currentKeyword = keyword;
254
+ this.paginationState.currentKeyword = keyword;
258
255
  append = false;
259
256
  }
260
257
 
261
- this._paginationState.isLoading = true;
262
- this._popup?.showLoading();
258
+ this.paginationState.isLoading = true;
259
+ this.popup?.showLoading();
263
260
 
264
- this._abortController?.abort();
265
- this._abortController = new AbortController();
261
+ this.abortController?.abort();
262
+ this.abortController = new AbortController();
266
263
 
267
- const page = this._paginationState.currentPage;
264
+ const page = this.paginationState.currentPage;
268
265
 
269
- const selectedValues = Array.from(this._select.selectedOptions)
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._selectBox.Selective.find(this._selectBox.container.targetElement))(keyword, page);
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._abortController.signal,
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._abortController.signal });
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._parseResponse(data);
296
+ const result = this.parseResponse(data);
300
297
 
301
298
  if (result.hasPagination) {
302
- this._paginationState.isPaginationEnabled = true;
303
- this._paginationState.currentPage = result.page;
304
- this._paginationState.totalPages = result.totalPages;
305
- this._paginationState.hasMore = result.hasMore;
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._paginationState.isPaginationEnabled = false;
304
+ this.paginationState.isPaginationEnabled = false;
308
305
  }
309
306
 
310
307
  this.applyAjaxResult(result.items, !!cfg.keepSelected, append);
311
308
 
312
- this._paginationState.isLoading = false;
313
- this._popup?.hideLoading();
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._paginationState.isLoading = false;
326
- this._popup?.hideLoading();
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 _parseResponse(data: any): ParseResponseResult {
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._select;
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 { DefaultConfig } from "../types/utils/istorage.type";
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, DefaultConfig> {
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 _privOnCollapsedChanged: Array<(evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void> = [];
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 {DefaultConfig} options - Configuration for the model.
26
+ * @param {SelectiveOptions} options - Configuration for the model.
27
27
  * @param {HTMLOptGroupElement} [targetElement] - The source <optgroup> element.
28
28
  */
29
- constructor(options: DefaultConfig, targetElement?: HTMLOptGroupElement) {
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._privOnCollapsedChanged.push(callback);
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._privOnCollapsedChanged);
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
  }