selective-ui 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/selective-ui.css +0 -6
  2. package/dist/selective-ui.css.map +1 -1
  3. package/dist/selective-ui.esm.js +252 -553
  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 +254 -555
  12. package/dist/selective-ui.umd.js.map +1 -1
  13. package/package.json +12 -12
  14. package/src/ts/adapter/mixed-adapter.ts +147 -68
  15. package/src/ts/components/accessorybox.ts +14 -11
  16. package/src/ts/components/directive.ts +1 -1
  17. package/src/ts/components/option-handle.ts +12 -9
  18. package/src/ts/components/placeholder.ts +5 -5
  19. package/src/ts/components/popup/empty-state.ts +5 -5
  20. package/src/ts/components/popup/loading-state.ts +5 -5
  21. package/src/ts/components/popup/popup.ts +138 -76
  22. package/src/ts/components/searchbox.ts +17 -13
  23. package/src/ts/components/selectbox.ts +242 -81
  24. package/src/ts/core/base/adapter.ts +39 -14
  25. package/src/ts/core/base/fenwick.ts +3 -2
  26. package/src/ts/core/base/lifecycle.ts +14 -4
  27. package/src/ts/core/base/model.ts +17 -15
  28. package/src/ts/core/base/recyclerview.ts +7 -5
  29. package/src/ts/core/base/view.ts +10 -5
  30. package/src/ts/core/base/virtual-recyclerview.ts +89 -37
  31. package/src/ts/core/model-manager.ts +48 -21
  32. package/src/ts/core/search-controller.ts +174 -56
  33. package/src/ts/global.ts +5 -8
  34. package/src/ts/index.ts +2 -2
  35. package/src/ts/models/group-model.ts +33 -8
  36. package/src/ts/models/option-model.ts +60 -19
  37. package/src/ts/services/dataset-observer.ts +6 -3
  38. package/src/ts/services/ea-observer.ts +1 -1
  39. package/src/ts/services/effector.ts +22 -12
  40. package/src/ts/services/refresher.ts +7 -3
  41. package/src/ts/services/resize-observer.ts +24 -11
  42. package/src/ts/services/select-observer.ts +2 -2
  43. package/src/ts/types/components/popup.type.ts +18 -1
  44. package/src/ts/types/components/searchbox.type.ts +43 -30
  45. package/src/ts/types/components/state.box.type.ts +1 -1
  46. package/src/ts/types/core/base/adapter.type.ts +13 -5
  47. package/src/ts/types/core/base/lifecycle.type.ts +1 -2
  48. package/src/ts/types/core/base/model.type.ts +3 -3
  49. package/src/ts/types/core/base/recyclerview.type.ts +7 -5
  50. package/src/ts/types/core/base/view.type.ts +6 -6
  51. package/src/ts/types/core/base/virtual-recyclerview.type.ts +45 -46
  52. package/src/ts/types/core/search-controller.type.ts +18 -2
  53. package/src/ts/types/css.d.ts +1 -0
  54. package/src/ts/types/plugins/plugin.type.ts +2 -2
  55. package/src/ts/types/services/effector.type.ts +25 -25
  56. package/src/ts/types/services/resize-observer.type.ts +23 -12
  57. package/src/ts/types/utils/callback-scheduler.type.ts +2 -2
  58. package/src/ts/types/utils/ievents.type.ts +1 -1
  59. package/src/ts/types/utils/istorage.type.ts +62 -60
  60. package/src/ts/types/utils/libs.type.ts +19 -17
  61. package/src/ts/types/utils/selective.type.ts +6 -3
  62. package/src/ts/types/views/view.group.type.ts +9 -5
  63. package/src/ts/types/views/view.option.type.ts +39 -17
  64. package/src/ts/utils/callback-scheduler.ts +12 -7
  65. package/src/ts/utils/ievents.ts +12 -5
  66. package/src/ts/utils/istorage.ts +5 -3
  67. package/src/ts/utils/libs.ts +122 -43
  68. package/src/ts/utils/selective.ts +15 -8
  69. package/src/ts/views/group-view.ts +11 -9
  70. package/src/ts/views/option-view.ts +37 -18
@@ -50,17 +50,19 @@ import { Lifecycle } from "./base/lifecycle";
50
50
  */
51
51
  export class ModelManager<
52
52
  TModel extends MixedItem,
53
- TAdapter extends Adapter<MixedItem, ViewContract<any>>
53
+ TAdapter extends Adapter<MixedItem, ViewContract<any>>,
54
54
  > extends Lifecycle {
55
55
  private privModelList: Array<MixedItem> = [];
56
56
 
57
57
  private privAdapter!: new (...args: any[]) => TAdapter;
58
58
 
59
- private privAdapterHandle: TAdapter | null = null;
59
+ private privAdapterHandle?: TAdapter;
60
60
 
61
- private privRecyclerView!: new (...args: any[]) => RecyclerViewContract<TAdapter>;
61
+ private privRecyclerView!: new (
62
+ ...args: any[]
63
+ ) => RecyclerViewContract<TAdapter>;
62
64
 
63
- private privRecyclerViewHandle: RecyclerViewContract<TAdapter> | null = null;
65
+ private privRecyclerViewHandle?: RecyclerViewContract<TAdapter>;
64
66
 
65
67
  private options: SelectiveOptions = null;
66
68
 
@@ -97,7 +99,9 @@ export class ModelManager<
97
99
  * @param {new (...args: any[]) => RecyclerViewContract<TAdapter>} recyclerView - The recycler view constructor.
98
100
  * @returns {void}
99
101
  */
100
- public setupRecyclerView(recyclerView: new (...args: any[]) => RecyclerViewContract<TAdapter>): void {
102
+ public setupRecyclerView(
103
+ recyclerView: new (...args: any[]) => RecyclerViewContract<TAdapter>,
104
+ ): void {
101
105
  this.privRecyclerView = recyclerView;
102
106
  }
103
107
 
@@ -113,7 +117,9 @@ export class ModelManager<
113
117
  * @param {Array<HTMLOptGroupElement | HTMLOptionElement>} modelData - Parsed DOM elements from the source `<select>`.
114
118
  * @returns {Array<GroupModel | OptionModel>} The ordered list of group and option models.
115
119
  */
116
- public createModelResources(modelData: Array<HTMLOptGroupElement | HTMLOptionElement>): Array<GroupModel | OptionModel> {
120
+ public createModelResources(
121
+ modelData: Array<HTMLOptGroupElement | HTMLOptionElement>,
122
+ ): Array<GroupModel | OptionModel> {
117
123
  if (this.is(LifecycleState.INITIALIZED)) {
118
124
  this.mount();
119
125
  }
@@ -123,14 +129,26 @@ export class ModelManager<
123
129
 
124
130
  modelData.forEach((data) => {
125
131
  if (data.tagName === "OPTGROUP") {
126
- currentGroup = new GroupModel(this.options, data as HTMLOptGroupElement);
132
+ currentGroup = new GroupModel(
133
+ this.options,
134
+ data as HTMLOptGroupElement,
135
+ );
127
136
  this.privModelList.push(currentGroup);
128
137
  } else if (data.tagName === "OPTION") {
129
- const optionModel = new OptionModel(this.options, data as HTMLOptionElement);
130
-
131
- const parentGroup = data["__parentGroup"] as HTMLOptGroupElement | undefined;
132
-
133
- if (parentGroup && currentGroup && parentGroup === currentGroup.targetElement) {
138
+ const optionModel = new OptionModel(
139
+ this.options,
140
+ data as HTMLOptionElement,
141
+ );
142
+
143
+ const parentGroup = data["__parentGroup"] as
144
+ | HTMLOptGroupElement
145
+ | undefined;
146
+
147
+ if (
148
+ parentGroup &&
149
+ currentGroup &&
150
+ parentGroup === currentGroup.targetElement
151
+ ) {
134
152
  currentGroup.addItem(optionModel);
135
153
  optionModel.group = currentGroup;
136
154
  } else {
@@ -155,7 +173,9 @@ export class ModelManager<
155
173
  * @returns {Promise<void>} Resolves when the adapter (if any) completes syncing.
156
174
  * @see Adapter#syncFromSource
157
175
  */
158
- public async replace(modelData: Array<HTMLOptGroupElement | HTMLOptionElement>): Promise<void> {
176
+ public async replace(
177
+ modelData: Array<HTMLOptGroupElement | HTMLOptionElement>,
178
+ ): Promise<void> {
159
179
  this.createModelResources(modelData);
160
180
 
161
181
  if (this.privAdapterHandle) {
@@ -199,9 +219,9 @@ export class ModelManager<
199
219
  public load<TExtra extends object = {}>(
200
220
  viewElement: HTMLElement,
201
221
  adapterOpt: Partial<TAdapter> = {},
202
- recyclerViewOpt: Partial<RecyclerViewContract<TAdapter>> & TExtra = {} as any
222
+ recyclerViewOpt: Partial<RecyclerViewContract<TAdapter>> &
223
+ TExtra = {} as any,
203
224
  ): void {
204
-
205
225
  this.privAdapterHandle = new this.privAdapter(this.privModelList);
206
226
  Object.assign(this.privAdapterHandle, adapterOpt);
207
227
 
@@ -230,7 +250,9 @@ export class ModelManager<
230
250
  * @returns {void}
231
251
  * @see Adapter#updateData
232
252
  */
233
- public updateModel(modelData: Array<HTMLOptGroupElement | HTMLOptionElement>): void {
253
+ public updateModel(
254
+ modelData: Array<HTMLOptGroupElement | HTMLOptionElement>,
255
+ ): void {
234
256
  const oldModels = this.privModelList;
235
257
  const newModels: Array<MixedItem> = [];
236
258
 
@@ -256,9 +278,10 @@ export class ModelManager<
256
278
 
257
279
  if (existingGroup) {
258
280
  // Label is used as key; keep original behavior.
259
- const hasLabelChange = existingGroup.label !== dataVset.label;
281
+ const hasLabelChange =
282
+ existingGroup.label !== dataVset.label;
260
283
  if (hasLabelChange) {
261
- existingGroup.updateTarget(dataVset)
284
+ existingGroup.updateTarget(dataVset);
262
285
  }
263
286
 
264
287
  existingGroup.position = position;
@@ -283,7 +306,9 @@ export class ModelManager<
283
306
  existingOption.updateTarget(dataVset);
284
307
  existingOption.position = position;
285
308
 
286
- const parentGroup = dataVset["__parentGroup"] as HTMLOptGroupElement;
309
+ const parentGroup = dataVset[
310
+ "__parentGroup"
311
+ ] as HTMLOptGroupElement;
287
312
 
288
313
  if (parentGroup && currentGroup) {
289
314
  currentGroup.addItem(existingOption);
@@ -298,7 +323,9 @@ export class ModelManager<
298
323
  const newOption = new OptionModel(this.options, dataVset);
299
324
  newOption.position = position;
300
325
 
301
- const parentGroup = dataVset["__parentGroup"] as HTMLOptGroupElement;
326
+ const parentGroup = dataVset[
327
+ "__parentGroup"
328
+ ] as HTMLOptGroupElement;
302
329
 
303
330
  if (parentGroup && currentGroup) {
304
331
  currentGroup.addItem(newOption);
@@ -438,4 +465,4 @@ export class ModelManager<
438
465
  public triggerChanged(event_name: string): Promise<void> {
439
466
  return this.privAdapterHandle?.changeProp(event_name);
440
467
  }
441
- }
468
+ }
@@ -4,7 +4,12 @@ import { GroupModel } from "../models/group-model";
4
4
  import { OptionModel } from "../models/option-model";
5
5
  import { LifecycleState } from "../types/core/base/lifecycle.type";
6
6
  import { MixedItem } from "../types/core/base/mixed-adapter.type";
7
- import { AjaxConfig, NormalizedAjaxItem, PaginationState, ParseResponseResult } from "../types/core/search-controller.type";
7
+ import {
8
+ AjaxConfig,
9
+ NormalizedAjaxItem,
10
+ PaginationState,
11
+ ParseResponseResult,
12
+ } from "../types/core/search-controller.type";
8
13
  import { Libs } from "../utils/libs";
9
14
  import { Lifecycle } from "./base/lifecycle";
10
15
  import { ModelManager } from "./model-manager";
@@ -52,13 +57,13 @@ export class SearchController extends Lifecycle {
52
57
  * AJAX configuration; when `null`, {@link search} falls back to local filtering.
53
58
  * @see {@link setAjax}
54
59
  */
55
- private ajaxConfig: AjaxConfig | null = null;
60
+ private ajaxConfig?: AjaxConfig;
56
61
 
57
62
  /** Abort handle used to cancel an in-flight AJAX request when a newer request starts. */
58
- private abortController: AbortController | null = null;
63
+ private abortController?: AbortController;
59
64
 
60
65
  /** Optional popup handle used for showing/hiding loading UI during remote operations. */
61
- private popup: Popup | null = null;
66
+ private popup?: Popup;
62
67
 
63
68
  /**
64
69
  * SelectBox handle used by custom data builder functions that require Selective context.
@@ -88,7 +93,11 @@ export class SearchController extends Lifecycle {
88
93
  * @param {ModelManager<MixedItem, any>} modelManager - Manager responsible for model resources and rendering refresh.
89
94
  * @param {SelectBox} selectBox - SelectBox handle used by configured AJAX data builders.
90
95
  */
91
- public constructor(selectElement: HTMLSelectElement, modelManager: ModelManager<MixedItem, any>, selectBox: SelectBox) {
96
+ public constructor(
97
+ selectElement: HTMLSelectElement,
98
+ modelManager: ModelManager<MixedItem, any>,
99
+ selectBox: SelectBox,
100
+ ) {
92
101
  super();
93
102
  this.initialize(selectElement, modelManager, selectBox);
94
103
  }
@@ -102,7 +111,11 @@ export class SearchController extends Lifecycle {
102
111
  * @param {SelectBox} selectBox - SelectBox handle.
103
112
  * @returns {void}
104
113
  */
105
- private initialize(selectElement: HTMLSelectElement, modelManager: ModelManager<MixedItem, any>, selectBox: SelectBox): void {
114
+ private initialize(
115
+ selectElement: HTMLSelectElement,
116
+ modelManager: ModelManager<MixedItem, any>,
117
+ selectBox: SelectBox,
118
+ ): void {
106
119
  this.select = selectElement;
107
120
  this.modelManager = modelManager;
108
121
  this.selectBox = selectBox;
@@ -137,9 +150,17 @@ export class SearchController extends Lifecycle {
137
150
  * - When AJAX is not configured, resolves with `{ success: false, ... }`.
138
151
  * - This method does not mutate the `<select>`; it only returns normalized items.
139
152
  */
140
- async loadByValues(values: string | string[]): Promise<{ success: boolean; items: NormalizedAjaxItem[]; message?: string }> {
153
+ async loadByValues(values: string | string[]): Promise<{
154
+ success: boolean;
155
+ items: NormalizedAjaxItem[];
156
+ message?: string;
157
+ }> {
141
158
  if (!this.ajaxConfig) {
142
- return { success: false, items: [], message: "Ajax not configured" };
159
+ return {
160
+ success: false,
161
+ items: [],
162
+ message: "Ajax not configured",
163
+ };
143
164
  }
144
165
 
145
166
  const valuesArray = Array.isArray(values) ? values : [values];
@@ -156,8 +177,12 @@ export class SearchController extends Lifecycle {
156
177
  values: valuesArray.join(","),
157
178
  load_by_values: "1",
158
179
  ...(typeof cfg.data === "function"
159
- ? cfg.data.bind(this.selectBox.Selective.find(this.selectBox.container.targetElement))("", 0)
160
- : cfg.data ?? {}),
180
+ ? cfg.data.bind(
181
+ this.selectBox.Selective.find(
182
+ this.selectBox.container.targetElement,
183
+ ),
184
+ )("", 0)
185
+ : (cfg.data ?? {})),
161
186
  };
162
187
  }
163
188
 
@@ -165,18 +190,23 @@ export class SearchController extends Lifecycle {
165
190
 
166
191
  if ((cfg.method ?? "GET") === "POST") {
167
192
  const formData = new URLSearchParams();
168
- Object.keys(payload).forEach((key) => formData.append(key, String(payload[key])));
193
+ Object.keys(payload).forEach((key) =>
194
+ formData.append(key, String(payload[key])),
195
+ );
169
196
  response = await fetch(cfg.url, {
170
197
  method: "POST",
171
198
  body: formData,
172
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
199
+ headers: {
200
+ "Content-Type": "application/x-www-form-urlencoded",
201
+ },
173
202
  });
174
203
  } else {
175
204
  const params = new URLSearchParams(payload).toString();
176
205
  response = await fetch(`${cfg.url}?${params}`);
177
206
  }
178
207
 
179
- if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
208
+ if (!response.ok)
209
+ throw new Error(`HTTP error! status: ${response.status}`);
180
210
 
181
211
  const data = await response.json();
182
212
  const result = this.parseResponse(data);
@@ -197,7 +227,10 @@ export class SearchController extends Lifecycle {
197
227
  * @param {string[]} values - Values to check.
198
228
  * @returns {{ existing: string[]; missing: string[] }} Partitioned result.
199
229
  */
200
- public checkMissingValues(values: string[]): { existing: string[]; missing: string[] } {
230
+ public checkMissingValues(values: string[]): {
231
+ existing: string[];
232
+ missing: string[];
233
+ } {
201
234
  const allOptions = Array.from(this.select.options);
202
235
  const existingValues = allOptions.map((opt) => opt.value);
203
236
 
@@ -211,10 +244,10 @@ export class SearchController extends Lifecycle {
211
244
  * Configures AJAX settings used for remote searching and pagination.
212
245
  * Setting `null` disables AJAX mode and causes {@link search} to use local filtering.
213
246
  *
214
- * @param {AjaxConfig | null} config - AJAX configuration (endpoint, method, data builders, keepSelected, ...).
247
+ * @param {AjaxConfig} config - AJAX configuration (endpoint, method, data builders, keepSelected, ...).
215
248
  * @returns {void}
216
249
  */
217
- public setAjax(config: AjaxConfig | null): void {
250
+ public setAjax(config?: AjaxConfig): void {
218
251
  this.ajaxConfig = config;
219
252
  }
220
253
 
@@ -272,7 +305,8 @@ export class SearchController extends Lifecycle {
272
305
 
273
306
  for (const m of modelList) {
274
307
  if (m instanceof OptionModel) flatOptions.push(m);
275
- else if (m instanceof GroupModel && Array.isArray(m.items)) flatOptions.push(...m.items);
308
+ else if (m instanceof GroupModel && Array.isArray(m.items))
309
+ flatOptions.push(...m.items);
276
310
  }
277
311
 
278
312
  flatOptions.forEach((opt) => {
@@ -289,7 +323,10 @@ export class SearchController extends Lifecycle {
289
323
  * @param {boolean} [append=false] - AJAX mode only: append results (next page) instead of replacing.
290
324
  * @returns {Promise<any>} Implementation-specific result object from the underlying strategy.
291
325
  */
292
- public async search(keyword: string, append: boolean = false): Promise<any> {
326
+ public async search(
327
+ keyword: string,
328
+ append: boolean = false,
329
+ ): Promise<any> {
293
330
  if (this.ajaxConfig) return this.ajaxSearch(keyword, append);
294
331
  return this.localSearch(keyword);
295
332
  }
@@ -305,10 +342,14 @@ export class SearchController extends Lifecycle {
305
342
  * @returns {Promise<any>} Result of the paginated request, or an error object when not applicable.
306
343
  */
307
344
  public async loadMore(): Promise<any> {
308
- if (!this.ajaxConfig) return { success: false, message: "Ajax not enabled" };
309
- if (this.paginationState.isLoading) return { success: false, message: "Already loading" };
310
- if (!this.paginationState.isPaginationEnabled) return { success: false, message: "Pagination not enabled" };
311
- if (!this.paginationState.hasMore) return { success: false, message: "No more data" };
345
+ if (!this.ajaxConfig)
346
+ return { success: false, message: "Ajax not enabled" };
347
+ if (this.paginationState.isLoading)
348
+ return { success: false, message: "Already loading" };
349
+ if (!this.paginationState.isPaginationEnabled)
350
+ return { success: false, message: "Pagination not enabled" };
351
+ if (!this.paginationState.hasMore)
352
+ return { success: false, message: "No more data" };
312
353
 
313
354
  this.paginationState.currentPage++;
314
355
  return this.ajaxSearch(this.paginationState.currentKeyword, true);
@@ -329,8 +370,11 @@ export class SearchController extends Lifecycle {
329
370
  * @returns {Promise<{ success: boolean; hasResults: boolean; isEmpty: boolean }>}
330
371
  * Summary result for UI consumers.
331
372
  */
332
- private async localSearch(keyword: string): Promise<{ success: boolean; hasResults: boolean; isEmpty: boolean }> {
333
- if (this.compareSearchTrigger(keyword)) this.paginationState.currentKeyword = keyword;
373
+ private async localSearch(
374
+ keyword: string,
375
+ ): Promise<{ success: boolean; hasResults: boolean; isEmpty: boolean }> {
376
+ if (this.compareSearchTrigger(keyword))
377
+ this.paginationState.currentKeyword = keyword;
334
378
 
335
379
  const lower = String(keyword ?? "").toLowerCase();
336
380
  const lowerNA = Libs.string2normalize(lower);
@@ -340,7 +384,8 @@ export class SearchController extends Lifecycle {
340
384
  const flatOptions: OptionModel[] = [];
341
385
  for (const m of modelList) {
342
386
  if (m instanceof OptionModel) flatOptions.push(m);
343
- else if (m instanceof GroupModel && Array.isArray(m.items)) flatOptions.push(...m.items);
387
+ else if (m instanceof GroupModel && Array.isArray(m.items))
388
+ flatOptions.push(...m.items);
344
389
  }
345
390
 
346
391
  let hasVisibleItems = false;
@@ -388,7 +433,10 @@ export class SearchController extends Lifecycle {
388
433
  * @param {boolean} [append=false] - Whether to append results (true = next page).
389
434
  * @returns {Promise<any>} Implementation-specific result object with pagination flags.
390
435
  */
391
- private async ajaxSearch(keyword: string, append: boolean = false): Promise<any> {
436
+ private async ajaxSearch(
437
+ keyword: string,
438
+ append: boolean = false,
439
+ ): Promise<any> {
392
440
  const cfg = this.ajaxConfig!;
393
441
  if (this.compareSearchTrigger(keyword)) {
394
442
  this.resetPagination();
@@ -410,11 +458,19 @@ export class SearchController extends Lifecycle {
410
458
 
411
459
  let payload: Record<string, any>;
412
460
  if (typeof cfg.data === "function") {
413
- const selectiveInstance = this.selectBox?.Selective?.find(this.selectBox?.container?.targetElement);
461
+ const selectiveInstance = this.selectBox?.Selective?.find(
462
+ this.selectBox?.container?.targetElement,
463
+ );
414
464
  payload = cfg.data.call(selectiveInstance, keyword, page);
415
- if (payload && typeof payload.selectedValue === "undefined") payload.selectedValue = selectedValues;
465
+ if (payload && typeof payload.selectedValue === "undefined")
466
+ payload.selectedValue = selectedValues;
416
467
  } else {
417
- payload = { search: keyword, page, selectedValue: selectedValues, ...(cfg.data ?? {}) };
468
+ payload = {
469
+ search: keyword,
470
+ page,
471
+ selectedValue: selectedValues,
472
+ ...(cfg.data ?? {}),
473
+ };
418
474
  }
419
475
 
420
476
  try {
@@ -422,16 +478,22 @@ export class SearchController extends Lifecycle {
422
478
 
423
479
  if ((cfg.method ?? "GET") === "POST") {
424
480
  const formData = new URLSearchParams();
425
- Object.keys(payload).forEach((key) => formData.append(key, String(payload[key])));
481
+ Object.keys(payload).forEach((key) =>
482
+ formData.append(key, String(payload[key])),
483
+ );
426
484
  response = await fetch(cfg.url, {
427
485
  method: "POST",
428
486
  body: formData,
429
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
487
+ headers: {
488
+ "Content-Type": "application/x-www-form-urlencoded",
489
+ },
430
490
  signal: this.abortController.signal,
431
491
  });
432
492
  } else {
433
493
  const params = new URLSearchParams(payload).toString();
434
- response = await fetch(`${cfg.url}?${params}`, { signal: this.abortController.signal });
494
+ response = await fetch(`${cfg.url}?${params}`, {
495
+ signal: this.abortController.signal,
496
+ });
435
497
  }
436
498
 
437
499
  const data = await response.json();
@@ -466,7 +528,8 @@ export class SearchController extends Lifecycle {
466
528
  this.paginationState.isLoading = false;
467
529
  this.popup?.hideLoading();
468
530
 
469
- if (error?.name === "AbortError") return { success: false, message: "Request aborted" };
531
+ if (error?.name === "AbortError")
532
+ return { success: false, message: "Request aborted" };
470
533
 
471
534
  console.error("Ajax search error:", error);
472
535
  return { success: false, message: error?.message };
@@ -502,7 +565,10 @@ export class SearchController extends Lifecycle {
502
565
  if (typeof data.page !== "undefined") {
503
566
  hasPagination = true;
504
567
  page = parseInt(data.page ?? 0, 10);
505
- totalPages = parseInt(data.totalPages ?? data.total_page ?? 1, 10);
568
+ totalPages = parseInt(
569
+ data.totalPages ?? data.total_page ?? 1,
570
+ 10,
571
+ );
506
572
  hasMore = page < totalPages - 1;
507
573
  }
508
574
  } else if (data.data && Array.isArray(data.data)) {
@@ -510,8 +576,11 @@ export class SearchController extends Lifecycle {
510
576
  if (typeof data.page !== "undefined") {
511
577
  hasPagination = true;
512
578
  page = parseInt(data.page ?? 0, 10);
513
- totalPages = parseInt(data.totalPages ?? data.total_page ?? 1, 10);
514
- hasMore = data.hasMore ?? (page < totalPages - 1);
579
+ totalPages = parseInt(
580
+ data.totalPages ?? data.total_page ?? 1,
581
+ 10,
582
+ );
583
+ hasMore = data.hasMore ?? page < totalPages - 1;
515
584
  }
516
585
  } else if (Array.isArray(data)) {
517
586
  items = data;
@@ -520,25 +589,53 @@ export class SearchController extends Lifecycle {
520
589
  if (data.pagination) {
521
590
  hasPagination = true;
522
591
  page = parseInt(data.pagination.page ?? 0, 10);
523
- totalPages = parseInt(data.pagination.totalPages ?? data.pagination.total_page ?? 1, 10);
524
- hasMore = data.pagination.hasMore ?? (page < totalPages - 1);
592
+ totalPages = parseInt(
593
+ data.pagination.totalPages ??
594
+ data.pagination.total_page ??
595
+ 1,
596
+ 10,
597
+ );
598
+ hasMore = data.pagination.hasMore ?? page < totalPages - 1;
525
599
  }
526
600
  }
527
601
 
528
602
  const normalized: NormalizedAjaxItem[] = items.map((item: any) => {
529
- if (item instanceof HTMLOptionElement || item instanceof HTMLOptGroupElement) return item;
530
-
531
- if (item.type === "optgroup" || item.isGroup || item.group || item.label) {
603
+ if (
604
+ item instanceof HTMLOptionElement ||
605
+ item instanceof HTMLOptGroupElement
606
+ )
607
+ return item;
608
+
609
+ if (
610
+ item.type === "optgroup" ||
611
+ item.isGroup ||
612
+ item.group ||
613
+ item.label
614
+ ) {
532
615
  const label = item.label ?? item.name ?? item.title ?? "";
533
616
  const dataObj = item.data ?? {};
534
- const opts = (item.options ?? item.items ?? []).map((opt: any) => ({
535
- value: opt.value ?? opt.id ?? opt.key ?? "",
536
- text: opt.text ?? opt.label ?? opt.name ?? opt.title ?? "",
537
- selected: opt.selected ?? false,
538
- data: opt.data ?? (opt.imgsrc ? { imgsrc: opt.imgsrc } : {}),
539
- }));
540
-
541
- return { type: "optgroup", label, data: dataObj, options: opts };
617
+ const opts = (item.options ?? item.items ?? []).map(
618
+ (opt: any) => ({
619
+ value: opt.value ?? opt.id ?? opt.key ?? "",
620
+ text:
621
+ opt.text ??
622
+ opt.label ??
623
+ opt.name ??
624
+ opt.title ??
625
+ "",
626
+ selected: opt.selected ?? false,
627
+ data:
628
+ opt.data ??
629
+ (opt.imgsrc ? { imgsrc: opt.imgsrc } : {}),
630
+ }),
631
+ );
632
+
633
+ return {
634
+ type: "optgroup",
635
+ label,
636
+ data: dataObj,
637
+ options: opts,
638
+ };
542
639
  }
543
640
 
544
641
  const dataObj = item.data ?? {};
@@ -574,19 +671,34 @@ export class SearchController extends Lifecycle {
574
671
  * @param {boolean} [append=false] - Append to existing options instead of replacing.
575
672
  * @returns {void}
576
673
  */
577
- public applyAjaxResult(items: NormalizedAjaxItem[], keepSelected: boolean, append: boolean = false): void {
674
+ public applyAjaxResult(
675
+ items: NormalizedAjaxItem[],
676
+ keepSelected: boolean,
677
+ append: boolean = false,
678
+ ): void {
578
679
  const select = this.select;
579
680
 
580
681
  let oldSelected: string[] = [];
581
- if (keepSelected) oldSelected = Array.from(select.selectedOptions).map((o) => o.value);
682
+ if (keepSelected)
683
+ oldSelected = Array.from(select.selectedOptions).map(
684
+ (o) => o.value,
685
+ );
582
686
 
583
687
  if (!append) select.innerHTML = "";
584
688
 
585
689
  items.forEach((item: any) => {
586
690
  // Skip empty item (defensive guard)
587
- if ((item["type"] === "option" || !item["type"]) && item["value"] === "" && item["text"] === "") return;
691
+ if (
692
+ (item["type"] === "option" || !item["type"]) &&
693
+ item["value"] === "" &&
694
+ item["text"] === ""
695
+ )
696
+ return;
588
697
 
589
- if (item instanceof HTMLOptionElement || item instanceof HTMLOptGroupElement) {
698
+ if (
699
+ item instanceof HTMLOptionElement ||
700
+ item instanceof HTMLOptGroupElement
701
+ ) {
590
702
  select.appendChild(item);
591
703
  return;
592
704
  }
@@ -613,7 +725,10 @@ export class SearchController extends Lifecycle {
613
725
  });
614
726
  }
615
727
 
616
- if (opt.selected || (keepSelected && oldSelected.includes(option.value))) {
728
+ if (
729
+ opt.selected ||
730
+ (keepSelected && oldSelected.includes(option.value))
731
+ ) {
617
732
  option.selected = true;
618
733
  }
619
734
 
@@ -633,7 +748,10 @@ export class SearchController extends Lifecycle {
633
748
  });
634
749
  }
635
750
 
636
- if (item.selected || (keepSelected && oldSelected.includes(option.value))) {
751
+ if (
752
+ item.selected ||
753
+ (keepSelected && oldSelected.includes(option.value))
754
+ ) {
637
755
  option.selected = true;
638
756
  }
639
757
 
@@ -667,4 +785,4 @@ export class SearchController extends Lifecycle {
667
785
 
668
786
  super.destroy();
669
787
  }
670
- }
788
+ }
package/src/ts/global.ts CHANGED
@@ -57,7 +57,7 @@ if (typeof globalThis.GLOBAL_SEUI == "undefined") {
57
57
  effector: Effector.bind(Effector),
58
58
  rebind: SECLASS.rebind.bind(SECLASS),
59
59
  registerPlugin: SECLASS.registerPlugin.bind(SECLASS),
60
- unregisterPlugin: SECLASS.unregisterPlugin.bind(SECLASS)
60
+ unregisterPlugin: SECLASS.unregisterPlugin.bind(SECLASS),
61
61
  } as SelectiveUIGlobal;
62
62
 
63
63
  let domInitialized = false;
@@ -68,9 +68,7 @@ if (typeof globalThis.GLOBAL_SEUI == "undefined") {
68
68
  document.addEventListener("mousedown", () => {
69
69
  const sels = Libs.getBindedCommand();
70
70
  if (sels.length > 0) {
71
- const actionApi = SECLASS.find(
72
- sels.join(", ")
73
- );
71
+ const actionApi = SECLASS.find(sels.join(", "));
74
72
  if (!actionApi.isEmpty) actionApi.close();
75
73
  }
76
74
  });
@@ -86,11 +84,10 @@ if (typeof globalThis.GLOBAL_SEUI == "undefined") {
86
84
  }
87
85
  }
88
86
  console.log(`[${__LIB_NAME__}] v${__LIB_VERSION__} loaded successfully`);
89
- }
90
- else {
87
+ } else {
91
88
  console.warn(
92
89
  `[${globalThis.GLOBAL_SEUI.name}] Already loaded (v${globalThis.GLOBAL_SEUI.version}). ` +
93
- `Using existing instance. Please remove duplicate <script> tags.`
90
+ `Using existing instance. Please remove duplicate <script> tags.`,
94
91
  );
95
92
  }
96
93
 
@@ -149,7 +146,7 @@ export function find(query: string): SelectiveActionApi {
149
146
  * // Destroy all instances
150
147
  * destroy();
151
148
  */
152
- export function destroy(query: string | null = null): void {
149
+ export function destroy(query?: string): void {
153
150
  globalThis.GLOBAL_SEUI.destroy(query);
154
151
  }
155
152
 
package/src/ts/index.ts CHANGED
@@ -98,7 +98,7 @@ export function find(query: string): SelectiveActionApi {
98
98
  * // Destroy all instances
99
99
  * destroy();
100
100
  */
101
- export function destroy(query: string | null = null): void {
101
+ export function destroy(query?: string): void {
102
102
  SECLASS.destroy(query);
103
103
  }
104
104
 
@@ -161,7 +161,7 @@ function init(): void {
161
161
  const sels = Libs.getBindedCommand();
162
162
  if (sels.length > 0) {
163
163
  const actionApi = SECLASS.find(
164
- sels.join(", ")
164
+ sels.join(", "),
165
165
  ) as SelectiveActionApi;
166
166
  if (!actionApi.isEmpty) actionApi.close();
167
167
  }