selective-ui 1.2.1 → 1.2.3

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 +498 -467
  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 +499 -468
  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 +50 -54
  16. package/src/ts/components/accessorybox.ts +51 -25
  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 +94 -109
  23. package/src/ts/components/searchbox.ts +14 -14
  24. package/src/ts/components/selectbox.ts +25 -29
  25. package/src/ts/core/base/adapter.ts +19 -19
  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 +55 -52
  30. package/src/ts/core/model-manager.ts +61 -54
  31. package/src/ts/core/search-controller.ts +69 -71
  32. package/src/ts/models/group-model.ts +21 -21
  33. package/src/ts/models/option-model.ts +30 -30
  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 +27 -27
  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 +27 -34
  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 +41 -21
  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
@@ -1,4 +1,4 @@
1
- /*! Selective UI v1.2.1 | MIT License */
1
+ /*! Selective UI v1.2.3 | MIT License */
2
2
  (function (global, factory) {
3
3
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
4
4
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
@@ -11,7 +11,7 @@
11
11
  class iStorage {
12
12
  constructor() {
13
13
  this.defaultConfig = {
14
- showPanel: true,
14
+ accessoryVisible: true,
15
15
  virtualScroll: true,
16
16
  accessoryStyle: "top",
17
17
  multiple: false,
@@ -149,11 +149,14 @@
149
149
  */
150
150
  run(key, ...params) {
151
151
  const executes = this.executeStored.get(key);
152
- if (!executes)
153
- return;
154
- if (!this.timerRunner.has(key))
152
+ if (!executes || executes.length === 0) {
153
+ return Promise.resolve();
154
+ }
155
+ if (!this.timerRunner.has(key)) {
155
156
  this.timerRunner.set(key, new Map());
157
+ }
156
158
  const runner = this.timerRunner.get(key);
159
+ const tasks = [];
157
160
  for (let i = 0; i < executes.length; i++) {
158
161
  const entry = executes[i];
159
162
  if (!entry)
@@ -161,20 +164,31 @@
161
164
  const prev = runner.get(i);
162
165
  if (prev)
163
166
  clearTimeout(prev);
164
- const timer = setTimeout(() => {
165
- entry.callback(params.length > 0 ? params : null);
166
- if (entry.once) {
167
- // Preserve index stability by leaving an empty slot.
168
- executes[i] = undefined;
169
- // Cleanup the timer handle for this index.
170
- const current = runner.get(i);
171
- if (current)
172
- clearTimeout(current);
173
- runner.delete(i);
174
- }
175
- }, entry.timeout);
176
- runner.set(i, timer);
167
+ const task = new Promise((resolve) => {
168
+ const timer = setTimeout(async () => {
169
+ try {
170
+ const resp = entry.callback(params.length > 0 ? params : null);
171
+ if (resp instanceof Promise) {
172
+ await resp;
173
+ }
174
+ }
175
+ catch { }
176
+ finally {
177
+ if (entry.once) {
178
+ executes[i] = undefined;
179
+ const current = runner.get(i);
180
+ if (current)
181
+ clearTimeout(current);
182
+ runner.delete(i);
183
+ }
184
+ resolve();
185
+ }
186
+ }, entry.timeout);
187
+ runner.set(i, timer);
188
+ });
189
+ tasks.push(task);
177
190
  }
191
+ return Promise.all(tasks).then(() => void 0);
178
192
  }
179
193
  /**
180
194
  * Clears callbacks and timers.
@@ -417,10 +431,20 @@
417
431
  for (const optionKey in myOptions) {
418
432
  const propValue = element[optionKey];
419
433
  if (propValue) {
420
- myOptions[optionKey] = propValue;
434
+ if (typeof myOptions[optionKey] === "boolean") {
435
+ myOptions[optionKey] = this.string2Boolean(propValue);
436
+ }
437
+ else {
438
+ myOptions[optionKey] = propValue;
439
+ }
421
440
  }
422
441
  else if (typeof element?.dataset?.[optionKey] !== "undefined") {
423
- myOptions[optionKey] = element.dataset[optionKey];
442
+ if (typeof myOptions[optionKey] === "boolean") {
443
+ myOptions[optionKey] = this.string2Boolean(element.dataset[optionKey]);
444
+ }
445
+ else {
446
+ myOptions[optionKey] = element.dataset[optionKey];
447
+ }
424
448
  }
425
449
  }
426
450
  return myOptions;
@@ -821,7 +845,7 @@
821
845
  */
822
846
  constructor(options) {
823
847
  this.node = null;
824
- this._options = null;
848
+ this.options = null;
825
849
  if (options)
826
850
  this.init(options);
827
851
  }
@@ -836,7 +860,7 @@
836
860
  classList: "selective-ui-placeholder",
837
861
  innerHTML: options.placeholder,
838
862
  });
839
- this._options = options;
863
+ this.options = options;
840
864
  }
841
865
  /**
842
866
  * Retrieves the current placeholder text from the configuration.
@@ -844,7 +868,7 @@
844
868
  * @returns {string} - The current placeholder text.
845
869
  */
846
870
  get() {
847
- return this._options?.placeholder ?? "";
871
+ return this.options?.placeholder ?? "";
848
872
  }
849
873
  /**
850
874
  * Updates the placeholder text and optionally saves it to the configuration.
@@ -854,12 +878,12 @@
854
878
  * @param {boolean} [isSave=true] - Whether to persist the new value in the configuration.
855
879
  */
856
880
  set(value, isSave = true) {
857
- if (!this.node || !this._options)
881
+ if (!this.node || !this.options)
858
882
  return;
859
883
  if (isSave)
860
- this._options.placeholder = value;
884
+ this.options.placeholder = value;
861
885
  const translated = Libs.tagTranslate(value);
862
- this.node.innerHTML = this._options.allowHtml ? translated : Libs.stripHtml(translated);
886
+ this.node.innerHTML = this.options.allowHtml ? translated : Libs.stripHtml(translated);
863
887
  }
864
888
  }
865
889
 
@@ -868,13 +892,13 @@
868
892
  */
869
893
  class Directive {
870
894
  constructor() {
871
- this.node = this._init();
895
+ this.node = this.init();
872
896
  }
873
897
  /**
874
898
  * Represents a directive button element used to toggle dropdown state.
875
899
  * Initializes a clickable node with appropriate ARIA attributes for accessibility.
876
900
  */
877
- _init() {
901
+ init() {
878
902
  // Libs.nodeCreator returns Element, but this node is always an HTMLElement in practice.
879
903
  return Libs.nodeCreator({
880
904
  node: "div",
@@ -906,8 +930,8 @@
906
930
  this.nodeMounted = null;
907
931
  this.node = null;
908
932
  this.options = null;
909
- this._ActionOnSelectAll = [];
910
- this._ActionOnDeSelectAll = [];
933
+ this.actionOnSelectAll = [];
934
+ this.actionOnDeSelectAll = [];
911
935
  if (options)
912
936
  this.init(options);
913
937
  }
@@ -928,7 +952,7 @@
928
952
  classList: "selective-ui-option-handle-item",
929
953
  textContent: options.textSelectAll,
930
954
  onclick: () => {
931
- iEvents.callFunctions(this._ActionOnSelectAll);
955
+ iEvents.callFunctions(this.actionOnSelectAll);
932
956
  },
933
957
  },
934
958
  },
@@ -938,7 +962,7 @@
938
962
  classList: "selective-ui-option-handle-item",
939
963
  textContent: options.textDeselectAll,
940
964
  onclick: () => {
941
- iEvents.callFunctions(this._ActionOnDeSelectAll);
965
+ iEvents.callFunctions(this.actionOnDeSelectAll);
942
966
  },
943
967
  },
944
968
  },
@@ -993,7 +1017,7 @@
993
1017
  */
994
1018
  OnSelectAll(action = null) {
995
1019
  if (typeof action === "function")
996
- this._ActionOnSelectAll.push(action);
1020
+ this.actionOnSelectAll.push(action);
997
1021
  }
998
1022
  /**
999
1023
  * Registers a callback to be executed when "Deselect All" is clicked.
@@ -1002,7 +1026,7 @@
1002
1026
  */
1003
1027
  OnDeSelectAll(action = null) {
1004
1028
  if (typeof action === "function")
1005
- this._ActionOnDeSelectAll.push(action);
1029
+ this.actionOnDeSelectAll.push(action);
1006
1030
  }
1007
1031
  }
1008
1032
 
@@ -1135,10 +1159,10 @@
1135
1159
  constructor() {
1136
1160
  this.isInit = false;
1137
1161
  this.element = null;
1138
- this._resizeObserver = null;
1139
- this._mutationObserver = null;
1162
+ this.resizeObserver = null;
1163
+ this.mutationObserver = null;
1140
1164
  this.isInit = true;
1141
- this._boundUpdateChanged = this._updateChanged.bind(this);
1165
+ this.boundUpdateChanged = this.updateChanged.bind(this);
1142
1166
  }
1143
1167
  /**
1144
1168
  * Callback invoked when the observed element's metrics change.
@@ -1152,7 +1176,7 @@
1152
1176
  * Computes the current metrics of the bound element (bounding rect + computed styles)
1153
1177
  * and forwards them to `onChanged(metrics)`.
1154
1178
  */
1155
- _updateChanged() {
1179
+ updateChanged() {
1156
1180
  const el = this.element;
1157
1181
  if (!el || typeof el.getBoundingClientRect !== "function") {
1158
1182
  const defaultMetrics = {
@@ -1201,7 +1225,7 @@
1201
1225
  * Manually triggers a metrics computation and notification via `onChanged`.
1202
1226
  */
1203
1227
  trigger() {
1204
- this._updateChanged();
1228
+ this.updateChanged();
1205
1229
  }
1206
1230
  /**
1207
1231
  * Starts observing the provided element for resize and style/class mutations,
@@ -1215,18 +1239,18 @@
1215
1239
  throw new Error("Invalid element");
1216
1240
  }
1217
1241
  this.element = element;
1218
- this._resizeObserver = new ResizeObserver(this._boundUpdateChanged);
1219
- this._resizeObserver.observe(element);
1220
- this._mutationObserver = new MutationObserver(this._boundUpdateChanged);
1221
- this._mutationObserver.observe(element, {
1242
+ this.resizeObserver = new ResizeObserver(this.boundUpdateChanged);
1243
+ this.resizeObserver.observe(element);
1244
+ this.mutationObserver = new MutationObserver(this.boundUpdateChanged);
1245
+ this.mutationObserver.observe(element, {
1222
1246
  attributes: true,
1223
1247
  attributeFilter: ["style", "class"],
1224
1248
  });
1225
- window.addEventListener("scroll", this._boundUpdateChanged, true);
1226
- window.addEventListener("resize", this._boundUpdateChanged);
1249
+ window.addEventListener("scroll", this.boundUpdateChanged, true);
1250
+ window.addEventListener("resize", this.boundUpdateChanged);
1227
1251
  if (window.visualViewport) {
1228
- window.visualViewport.addEventListener("resize", this._boundUpdateChanged);
1229
- window.visualViewport.addEventListener("scroll", this._boundUpdateChanged);
1252
+ window.visualViewport.addEventListener("resize", this.boundUpdateChanged);
1253
+ window.visualViewport.addEventListener("scroll", this.boundUpdateChanged);
1230
1254
  }
1231
1255
  }
1232
1256
  /**
@@ -1234,17 +1258,17 @@
1234
1258
  * and releases internal observer resources.
1235
1259
  */
1236
1260
  disconnect() {
1237
- this._resizeObserver?.disconnect();
1238
- this._mutationObserver?.disconnect();
1261
+ this.resizeObserver?.disconnect();
1262
+ this.mutationObserver?.disconnect();
1239
1263
  this.onChanged = (_metrics) => { };
1240
- window.removeEventListener("scroll", this._boundUpdateChanged, true);
1241
- window.removeEventListener("resize", this._boundUpdateChanged);
1264
+ window.removeEventListener("scroll", this.boundUpdateChanged, true);
1265
+ window.removeEventListener("resize", this.boundUpdateChanged);
1242
1266
  if (window.visualViewport) {
1243
- window.visualViewport.removeEventListener("resize", this._boundUpdateChanged);
1244
- window.visualViewport.removeEventListener("scroll", this._boundUpdateChanged);
1267
+ window.visualViewport.removeEventListener("resize", this.boundUpdateChanged);
1268
+ window.visualViewport.removeEventListener("scroll", this.boundUpdateChanged);
1245
1269
  }
1246
- this._resizeObserver = null;
1247
- this._mutationObserver = null;
1270
+ this.resizeObserver = null;
1271
+ this.mutationObserver = null;
1248
1272
  this.element = null;
1249
1273
  }
1250
1274
  }
@@ -1266,22 +1290,22 @@
1266
1290
  this.isCreated = false;
1267
1291
  this.optionAdapter = null;
1268
1292
  this.node = null;
1269
- this._effSvc = null;
1270
- this._resizeObser = null;
1271
- this._parent = null;
1293
+ this.effSvc = null;
1294
+ this.resizeObser = null;
1295
+ this.parent = null;
1272
1296
  this.optionHandle = null;
1273
1297
  this.emptyState = null;
1274
1298
  this.loadingState = null;
1275
1299
  this.recyclerView = null;
1276
- this._optionsContainer = null;
1277
- this._scrollListener = null;
1278
- this._hideLoadHandle = null;
1300
+ this.optionsContainer = null;
1301
+ this.scrollListener = null;
1302
+ this.hideLoadHandle = null;
1279
1303
  this.virtualScrollConfig = {
1280
1304
  estimateItemHeight: 36,
1281
1305
  overscan: 8,
1282
1306
  dynamicHeights: true
1283
1307
  };
1284
- this._modelManager = modelManager;
1308
+ this.modelManager = modelManager;
1285
1309
  if (select && options) {
1286
1310
  this.init(select, options);
1287
1311
  }
@@ -1294,7 +1318,7 @@
1294
1318
  * @param {object} options - Configuration for panel, IDs, multiple mode, and texts.
1295
1319
  */
1296
1320
  init(select, options) {
1297
- if (!this._modelManager)
1321
+ if (!this.modelManager)
1298
1322
  throw new Error("Popup requires a ModelManager instance.");
1299
1323
  this.optionHandle = new OptionHandle(options);
1300
1324
  this.emptyState = new EmptyState(options);
@@ -1322,8 +1346,8 @@
1322
1346
  },
1323
1347
  }, null);
1324
1348
  this.node = nodeMounted.view;
1325
- this._optionsContainer = nodeMounted.tags.OptionsContainer;
1326
- this._parent = Libs.getBinderMap(select);
1349
+ this.optionsContainer = nodeMounted.tags.OptionsContainer;
1350
+ this.parent = Libs.getBinderMap(select);
1327
1351
  this.options = options;
1328
1352
  const recyclerViewOpt = options.virtualScroll
1329
1353
  ? {
@@ -1334,8 +1358,8 @@
1334
1358
  }
1335
1359
  : {};
1336
1360
  // Load ModelManager resources into container
1337
- this._modelManager.load(this._optionsContainer, { isMultiple: options.multiple }, recyclerViewOpt);
1338
- const MMResources = this._modelManager.getResources();
1361
+ this.modelManager.load(this.optionsContainer, { isMultiple: options.multiple }, recyclerViewOpt);
1362
+ const MMResources = this.modelManager.getResources();
1339
1363
  this.optionAdapter = MMResources.adapter;
1340
1364
  this.recyclerView = MMResources.recyclerView;
1341
1365
  this.optionHandle.OnSelectAll(() => {
@@ -1344,21 +1368,21 @@
1344
1368
  this.optionHandle.OnDeSelectAll(() => {
1345
1369
  MMResources.adapter.checkAll(false);
1346
1370
  });
1347
- this._setupEmptyStateLogic();
1371
+ this.setupEmptyStateLogic();
1348
1372
  }
1349
1373
  /**
1350
1374
  * Shows the loading state and temporarily skips model events.
1351
1375
  * Adjusts size based on current visibility stats and triggers a resize.
1352
1376
  */
1353
1377
  async showLoading() {
1354
- if (!this.options || !this.loadingState || !this.optionHandle || !this.optionAdapter || !this._modelManager)
1378
+ if (!this.options || !this.loadingState || !this.optionHandle || !this.optionAdapter || !this.modelManager)
1355
1379
  return;
1356
- if (this._hideLoadHandle)
1357
- clearTimeout(this._hideLoadHandle);
1358
- this._modelManager.skipEvent(true);
1380
+ if (this.hideLoadHandle)
1381
+ clearTimeout(this.hideLoadHandle);
1382
+ this.modelManager.skipEvent(true);
1359
1383
  if (Libs.string2Boolean(this.options.loadingfield) === false)
1360
1384
  return;
1361
- // this._updateEmptyState({isEmpty: false, hasVisible: true});
1385
+ // this.updateEmptyState({isEmpty: false, hasVisible: true});
1362
1386
  this.emptyState.hide();
1363
1387
  this.loadingState.show(this.optionAdapter.getVisibilityStats().hasVisible);
1364
1388
  // this.optionHandle.hide();
@@ -1369,31 +1393,31 @@
1369
1393
  * updates empty state based on adapter visibility stats, and triggers a resize.
1370
1394
  */
1371
1395
  async hideLoading() {
1372
- if (!this.options || !this.loadingState || !this.optionAdapter || !this._modelManager)
1396
+ if (!this.options || !this.loadingState || !this.optionAdapter || !this.modelManager)
1373
1397
  return;
1374
- if (this._hideLoadHandle)
1375
- clearTimeout(this._hideLoadHandle);
1376
- this._hideLoadHandle = setTimeout(() => {
1377
- this._modelManager?.skipEvent(false);
1398
+ if (this.hideLoadHandle)
1399
+ clearTimeout(this.hideLoadHandle);
1400
+ this.hideLoadHandle = setTimeout(() => {
1401
+ this.modelManager?.skipEvent(false);
1378
1402
  this.loadingState?.hide();
1379
1403
  const stats = this.optionAdapter?.getVisibilityStats();
1380
- this._updateEmptyState(stats ?? undefined);
1404
+ this.updateEmptyState(stats ?? undefined);
1381
1405
  this.triggerResize();
1382
- }, 200);
1406
+ }, this.options.animationtime);
1383
1407
  }
1384
1408
  /**
1385
1409
  * Subscribes to adapter visibility and item changes to keep the empty state in sync.
1386
1410
  * Triggers resize when items change to reflect layout updates.
1387
1411
  */
1388
- _setupEmptyStateLogic() {
1412
+ setupEmptyStateLogic() {
1389
1413
  if (!this.optionAdapter)
1390
1414
  return;
1391
1415
  this.optionAdapter.onVisibilityChanged((stats) => {
1392
- this._updateEmptyState(stats);
1416
+ this.updateEmptyState(stats);
1393
1417
  });
1394
1418
  this.optionAdapter.onPropChanged("items", () => {
1395
1419
  const stats = this.optionAdapter.getVisibilityStats();
1396
- this._updateEmptyState(stats);
1420
+ this.updateEmptyState(stats);
1397
1421
  this.triggerResize();
1398
1422
  });
1399
1423
  }
@@ -1403,23 +1427,23 @@
1403
1427
  *
1404
1428
  * @param {VisibilityStats|undefined} stats - Visibility stats; computed if omitted.
1405
1429
  */
1406
- _updateEmptyState(stats) {
1407
- if (!this.optionAdapter || !this.emptyState || !this.optionHandle || !this._optionsContainer)
1430
+ updateEmptyState(stats) {
1431
+ if (!this.optionAdapter || !this.emptyState || !this.optionHandle || !this.optionsContainer)
1408
1432
  return;
1409
1433
  const s = stats ?? this.optionAdapter.getVisibilityStats();
1410
1434
  if (s.isEmpty) {
1411
1435
  this.emptyState.show("nodata");
1412
- this._optionsContainer.classList.add("hide");
1436
+ this.optionsContainer.classList.add("hide");
1413
1437
  this.optionHandle.hide();
1414
1438
  }
1415
1439
  else if (!s.hasVisible) {
1416
1440
  this.emptyState.show("notfound");
1417
- this._optionsContainer.classList.add("hide");
1441
+ this.optionsContainer.classList.add("hide");
1418
1442
  this.optionHandle.hide();
1419
1443
  }
1420
1444
  else {
1421
1445
  this.emptyState.hide();
1422
- this._optionsContainer.classList.remove("hide");
1446
+ this.optionsContainer.classList.remove("hide");
1423
1447
  this.optionHandle.refresh();
1424
1448
  }
1425
1449
  }
@@ -1439,20 +1463,20 @@
1439
1463
  * Injects an effector service used to perform side effects (e.g., animations or external actions).
1440
1464
  */
1441
1465
  setupEffector(effectorSvc) {
1442
- this._effSvc = effectorSvc;
1466
+ this.effSvc = effectorSvc;
1443
1467
  }
1444
1468
  /**
1445
1469
  * Opens the popup: creates and attaches DOM if needed, initializes observers and effector,
1446
1470
  * computes position and dimensions, and runs expand animation. Invokes callback on completion.
1447
1471
  */
1448
1472
  open(callback = null, isShowEmptyState) {
1449
- if (!this.node || !this.options || !this.optionHandle || !this._parent || !this._effSvc)
1473
+ if (!this.node || !this.options || !this.optionHandle || !this.parent || !this.effSvc)
1450
1474
  return;
1451
1475
  if (!this.isCreated) {
1452
1476
  document.body.appendChild(this.node);
1453
1477
  this.isCreated = true;
1454
- this._resizeObser = new ResizeObserverService();
1455
- this._effSvc.setElement(this.node);
1478
+ this.resizeObser = new ResizeObserverService();
1479
+ this.effSvc.setElement(this.node);
1456
1480
  this.node.addEventListener("mousedown", (e) => {
1457
1481
  e.stopPropagation();
1458
1482
  e.preventDefault();
@@ -1460,11 +1484,11 @@
1460
1484
  }
1461
1485
  this.optionHandle.refresh();
1462
1486
  if (isShowEmptyState) {
1463
- this._updateEmptyState();
1487
+ this.updateEmptyState();
1464
1488
  }
1465
- const location = this._getParentLocation();
1466
- const { position, top, maxHeight, realHeight } = this._calculatePosition(location);
1467
- this._effSvc.expand({
1489
+ const location = this.getParentLocation();
1490
+ const { position, top, maxHeight, realHeight } = this.calculatePosition(location);
1491
+ this.effSvc.expand({
1468
1492
  duration: this.options.animationtime,
1469
1493
  display: "flex",
1470
1494
  width: location.width,
@@ -1474,14 +1498,14 @@
1474
1498
  realHeight,
1475
1499
  position,
1476
1500
  onComplete: () => {
1477
- if (!this._resizeObser || !this._parent)
1501
+ if (!this.resizeObser || !this.parent)
1478
1502
  return;
1479
- this._resizeObser.onChanged = (_metrics) => {
1503
+ this.resizeObser.onChanged = (_metrics) => {
1480
1504
  // Recompute from parent each time to keep behavior identical.
1481
- const loc = this._getParentLocation();
1482
- this._handleResize(loc);
1505
+ const loc = this.getParentLocation();
1506
+ this.handleResize(loc);
1483
1507
  };
1484
- this._resizeObser.connect(this._parent.container.tags.ViewPanel);
1508
+ this.resizeObser.connect(this.parent.container.tags.ViewPanel);
1485
1509
  callback?.();
1486
1510
  const rv = this.recyclerView;
1487
1511
  rv?.resume?.();
@@ -1493,12 +1517,12 @@
1493
1517
  * Safely no-ops if the popup has not been created.
1494
1518
  */
1495
1519
  close(callback = null) {
1496
- if (!this.isCreated || !this.options || !this._resizeObser || !this._effSvc)
1520
+ if (!this.isCreated || !this.options || !this.resizeObser || !this.effSvc)
1497
1521
  return;
1498
1522
  const rv = this.recyclerView;
1499
1523
  rv?.suspend?.();
1500
- this._resizeObser.disconnect();
1501
- this._effSvc.collapse({
1524
+ this.resizeObser.disconnect();
1525
+ this.effSvc.collapse({
1502
1526
  duration: this.options.animationtime,
1503
1527
  onComplete: callback ?? undefined,
1504
1528
  });
@@ -1509,7 +1533,7 @@
1509
1533
  */
1510
1534
  triggerResize() {
1511
1535
  if (this.isCreated)
1512
- this._resizeObser?.trigger();
1536
+ this.resizeObser?.trigger();
1513
1537
  }
1514
1538
  /**
1515
1539
  * Enables infinite scroll by listening to container scroll events and loading more data
@@ -1521,7 +1545,7 @@
1521
1545
  setupInfiniteScroll(searchController, _options) {
1522
1546
  if (!this.node)
1523
1547
  return;
1524
- this._scrollListener = async () => {
1548
+ this.scrollListener = async () => {
1525
1549
  const state = searchController.getPaginationState();
1526
1550
  if (!state.isPaginationEnabled)
1527
1551
  return;
@@ -1539,7 +1563,7 @@
1539
1563
  }
1540
1564
  }
1541
1565
  };
1542
- this.node.addEventListener("scroll", this._scrollListener);
1566
+ this.node.addEventListener("scroll", this.scrollListener);
1543
1567
  }
1544
1568
  /**
1545
1569
  * Completely tear down the popup instance and release all resources.
@@ -1554,24 +1578,24 @@
1554
1578
  * Safe to call multiple times; all operations are guarded via optional chaining.
1555
1579
  */
1556
1580
  detroy() {
1557
- if (this._hideLoadHandle) {
1558
- clearTimeout(this._hideLoadHandle);
1559
- this._hideLoadHandle = null;
1581
+ if (this.hideLoadHandle) {
1582
+ clearTimeout(this.hideLoadHandle);
1583
+ this.hideLoadHandle = null;
1560
1584
  }
1561
- if (this.node && this._scrollListener) {
1562
- this.node.removeEventListener("scroll", this._scrollListener);
1563
- this._scrollListener = null;
1585
+ if (this.node && this.scrollListener) {
1586
+ this.node.removeEventListener("scroll", this.scrollListener);
1587
+ this.scrollListener = null;
1564
1588
  }
1565
1589
  try {
1566
- this._resizeObser?.disconnect();
1590
+ this.resizeObser?.disconnect();
1567
1591
  }
1568
1592
  catch (_) { }
1569
- this._resizeObser = null;
1593
+ this.resizeObser = null;
1570
1594
  try {
1571
- this._effSvc?.setElement?.(null);
1595
+ this.effSvc?.setElement?.(null);
1572
1596
  }
1573
1597
  catch (_) { }
1574
- this._effSvc = null;
1598
+ this.effSvc = null;
1575
1599
  if (this.node) {
1576
1600
  try {
1577
1601
  const clone = this.node.cloneNode(true);
@@ -1583,20 +1607,20 @@
1583
1607
  }
1584
1608
  }
1585
1609
  this.node = null;
1586
- this._optionsContainer = null;
1610
+ this.optionsContainer = null;
1587
1611
  try {
1588
- this._modelManager?.skipEvent?.(false);
1612
+ this.modelManager?.skipEvent?.(false);
1589
1613
  this.recyclerView?.clear?.();
1590
1614
  this.recyclerView = null;
1591
1615
  this.optionAdapter = null;
1592
1616
  this.node.remove();
1593
1617
  }
1594
1618
  catch (_) { }
1595
- this._modelManager = null;
1619
+ this.modelManager = null;
1596
1620
  this.optionHandle = null;
1597
1621
  this.emptyState = null;
1598
1622
  this.loadingState = null;
1599
- this._parent = null;
1623
+ this.parent = null;
1600
1624
  this.options = null;
1601
1625
  this.isCreated = false;
1602
1626
  }
@@ -1604,8 +1628,8 @@
1604
1628
  * Computes the parent panel's location and box metrics, including size, position,
1605
1629
  * padding, and border, accounting for iOS visual viewport offsets.
1606
1630
  */
1607
- _getParentLocation() {
1608
- const viewPanel = this._parent.container.tags.ViewPanel;
1631
+ getParentLocation() {
1632
+ const viewPanel = this.parent.container.tags.ViewPanel;
1609
1633
  const rect = viewPanel.getBoundingClientRect();
1610
1634
  const style = window.getComputedStyle(viewPanel);
1611
1635
  return {
@@ -1631,13 +1655,13 @@
1631
1655
  * Determines popup placement (top/bottom) and height constraints based on available viewport space,
1632
1656
  * content size, and configured min/max heights; returns final position, top, and heights.
1633
1657
  */
1634
- _calculatePosition(location) {
1658
+ calculatePosition(location) {
1635
1659
  const vv = window.visualViewport;
1636
1660
  const is_ios = Libs.IsIOS();
1637
1661
  const viewportHeight = vv?.height ?? window.innerHeight;
1638
1662
  const gap = 3;
1639
1663
  const safeMargin = 15;
1640
- const dimensions = this._effSvc.getHiddenDimensions("flex");
1664
+ const dimensions = this.effSvc.getHiddenDimensions("flex");
1641
1665
  const contentHeight = dimensions.scrollHeight;
1642
1666
  const configMaxHeight = parseFloat(this.options?.panelHeight ?? "220") || 220;
1643
1667
  const configMinHeight = parseFloat(this.options?.panelMinHeight ?? "100") || 100;
@@ -1676,11 +1700,11 @@
1676
1700
  * Handles parent resize events by recalculating placement and dimensions,
1677
1701
  * then animates the popup to the new size and position.
1678
1702
  */
1679
- _handleResize(location) {
1680
- if (!this.options || !this._effSvc)
1703
+ handleResize(location) {
1704
+ if (!this.options || !this.effSvc)
1681
1705
  return;
1682
- const { position, top, maxHeight, realHeight } = this._calculatePosition(location);
1683
- this._effSvc.resize({
1706
+ const { position, top, maxHeight, realHeight } = this.calculatePosition(location);
1707
+ this.effSvc.resize({
1684
1708
  duration: this.options.animationtime,
1685
1709
  width: location.width,
1686
1710
  left: location.left,
@@ -1854,8 +1878,8 @@
1854
1878
  * @param {string|HTMLElement|null} [query] - A CSS selector or the target element to control.
1855
1879
  */
1856
1880
  constructor(query = null) {
1857
- this._timeOut = null;
1858
- this._resizeTimeout = null;
1881
+ this.timeOut = null;
1882
+ this.resizeTimeout = null;
1859
1883
  this._isAnimating = false;
1860
1884
  if (query)
1861
1885
  this.setElement(query);
@@ -1882,13 +1906,13 @@
1882
1906
  * @returns {this} - The effector instance for chaining.
1883
1907
  */
1884
1908
  cancel() {
1885
- if (this._timeOut) {
1886
- clearTimeout(this._timeOut);
1887
- this._timeOut = null;
1909
+ if (this.timeOut) {
1910
+ clearTimeout(this.timeOut);
1911
+ this.timeOut = null;
1888
1912
  }
1889
- if (this._resizeTimeout) {
1890
- clearTimeout(this._resizeTimeout);
1891
- this._resizeTimeout = null;
1913
+ if (this.resizeTimeout) {
1914
+ clearTimeout(this.resizeTimeout);
1915
+ this.resizeTimeout = null;
1892
1916
  }
1893
1917
  this._isAnimating = false;
1894
1918
  return this;
@@ -1963,7 +1987,7 @@
1963
1987
  opacity: "1",
1964
1988
  overflow: isScrollable ? "auto" : "hidden",
1965
1989
  });
1966
- this._timeOut = setTimeout(() => {
1990
+ this.timeOut = setTimeout(() => {
1967
1991
  this.element.style.transition = "none";
1968
1992
  this._isAnimating = false;
1969
1993
  onComplete?.();
@@ -1995,7 +2019,7 @@
1995
2019
  opacity: "0",
1996
2020
  overflow: isScrollable ? "auto" : "hidden",
1997
2021
  });
1998
- this._timeOut = setTimeout(() => {
2022
+ this.timeOut = setTimeout(() => {
1999
2023
  Object.assign(this.element.style, {
2000
2024
  display: "none",
2001
2025
  transition: "none",
@@ -2031,7 +2055,7 @@
2031
2055
  overflow: "hidden",
2032
2056
  });
2033
2057
  });
2034
- this._timeOut = setTimeout(() => {
2058
+ this.timeOut = setTimeout(() => {
2035
2059
  Object.assign(this.element.style, {
2036
2060
  width: "",
2037
2061
  overflow: "",
@@ -2065,7 +2089,7 @@
2065
2089
  overflow: "hidden",
2066
2090
  });
2067
2091
  });
2068
- this._timeOut = setTimeout(() => {
2092
+ this.timeOut = setTimeout(() => {
2069
2093
  Object.assign(this.element.style, {
2070
2094
  width: "",
2071
2095
  overflow: "",
@@ -2095,7 +2119,7 @@
2095
2119
  if (isPositionChanged) {
2096
2120
  this.element.style.transition = `top ${duration}ms ease-out, height ${duration}ms ease-out, max-height ${duration}ms ease-out;`;
2097
2121
  }
2098
- setTimeout(() => {
2122
+ requestAnimationFrame(() => {
2099
2123
  const styles = {
2100
2124
  width: `${width}px`,
2101
2125
  left: `${left}px`,
@@ -2109,7 +2133,7 @@
2109
2133
  styles.transition = `height ${duration}ms, top ${duration}ms`;
2110
2134
  }
2111
2135
  else {
2112
- this._resizeTimeout = setTimeout(() => {
2136
+ this.resizeTimeout = setTimeout(() => {
2113
2137
  if (this.element?.style) {
2114
2138
  this.element.style.transition = null;
2115
2139
  }
@@ -2117,7 +2141,7 @@
2117
2141
  }
2118
2142
  Object.assign(this.element.style, styles);
2119
2143
  if (animate && (isPositionChanged || heightDiff > 1)) {
2120
- this._resizeTimeout = setTimeout(() => {
2144
+ this.resizeTimeout = setTimeout(() => {
2121
2145
  this.element.style.transition = null;
2122
2146
  if (isPositionChanged)
2123
2147
  delete this.element.style.transition;
@@ -2129,7 +2153,7 @@
2129
2153
  delete this.element.style.transition;
2130
2154
  onComplete?.();
2131
2155
  }
2132
- }, 20);
2156
+ });
2133
2157
  return this;
2134
2158
  }
2135
2159
  /**
@@ -2212,7 +2236,7 @@
2212
2236
  * Initializes a group model with options and an optional <optgroup> target.
2213
2237
  * Reads the label and collapsed state from the target element's attributes/dataset.
2214
2238
  *
2215
- * @param {DefaultConfig} options - Configuration for the model.
2239
+ * @param {SelectiveOptions} options - Configuration for the model.
2216
2240
  * @param {HTMLOptGroupElement} [targetElement] - The source <optgroup> element.
2217
2241
  */
2218
2242
  constructor(options, targetElement) {
@@ -2220,7 +2244,7 @@
2220
2244
  this.label = "";
2221
2245
  this.items = [];
2222
2246
  this.collapsed = false;
2223
- this._privOnCollapsedChanged = [];
2247
+ this.privOnCollapsedChanged = [];
2224
2248
  if (targetElement) {
2225
2249
  this.label = targetElement.label;
2226
2250
  this.collapsed = Libs.string2Boolean(targetElement.dataset?.collapsed);
@@ -2283,7 +2307,7 @@
2283
2307
  * @param {(evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void} callback - Listener for collapse changes.
2284
2308
  */
2285
2309
  onCollapsedChanged(callback) {
2286
- this._privOnCollapsedChanged.push(callback);
2310
+ this.privOnCollapsedChanged.push(callback);
2287
2311
  }
2288
2312
  /**
2289
2313
  * Toggles the group's collapsed state, updates the view, and notifies registered listeners.
@@ -2291,7 +2315,7 @@
2291
2315
  toggleCollapse() {
2292
2316
  this.collapsed = !this.collapsed;
2293
2317
  this.view?.setCollapsed(this.collapsed);
2294
- iEvents.callEvent([this, this.collapsed], ...this._privOnCollapsedChanged);
2318
+ iEvents.callEvent([this, this.collapsed], ...this.privOnCollapsedChanged);
2295
2319
  }
2296
2320
  /**
2297
2321
  * Adds an option item to this group and sets its back-reference to the group.
@@ -2337,9 +2361,9 @@
2337
2361
  */
2338
2362
  constructor(options, targetElement = null, view = null) {
2339
2363
  super(options, targetElement, view);
2340
- this._privOnSelected = [];
2341
- this._privOnInternalSelected = [];
2342
- this._privOnVisibilityChanged = [];
2364
+ this.privOnSelected = [];
2365
+ this.privOnInternalSelected = [];
2366
+ this.privOnVisibilityChanged = [];
2343
2367
  this._visible = true;
2344
2368
  this._highlighted = false;
2345
2369
  this.group = null;
@@ -2387,7 +2411,7 @@
2387
2411
  */
2388
2412
  set selected(value) {
2389
2413
  this.selectedNonTrigger = value;
2390
- iEvents.callEvent([this, value], ...this._privOnSelected);
2414
+ iEvents.callEvent([this, value], ...this.privOnSelected);
2391
2415
  }
2392
2416
  /**
2393
2417
  * Gets whether the option is currently visible in the UI.
@@ -2409,7 +2433,7 @@
2409
2433
  const viewEl = this.view?.getView?.();
2410
2434
  if (viewEl)
2411
2435
  viewEl.classList.toggle("hide", !value);
2412
- iEvents.callEvent([this, value], ...this._privOnVisibilityChanged);
2436
+ iEvents.callEvent([this, value], ...this.privOnVisibilityChanged);
2413
2437
  }
2414
2438
  /**
2415
2439
  * Gets the selected state without triggering external listeners (alias of selected).
@@ -2437,7 +2461,7 @@
2437
2461
  }
2438
2462
  if (this.targetElement)
2439
2463
  this.targetElement.selected = value;
2440
- iEvents.callEvent([this, value], ...this._privOnInternalSelected);
2464
+ iEvents.callEvent([this, value], ...this.privOnInternalSelected);
2441
2465
  }
2442
2466
  /**
2443
2467
  * Returns the display text for the option, applying tag translation and optional HTML allowance.
@@ -2495,7 +2519,7 @@
2495
2519
  * @param {(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void} callback - Selection listener.
2496
2520
  */
2497
2521
  onSelected(callback) {
2498
- this._privOnSelected.push(callback);
2522
+ this.privOnSelected.push(callback);
2499
2523
  }
2500
2524
  /**
2501
2525
  * Registers a listener invoked when internal selection changes (via setter `selectedNonTrigger`).
@@ -2503,7 +2527,7 @@
2503
2527
  * @param {(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void} callback - Internal selection listener.
2504
2528
  */
2505
2529
  onInternalSelected(callback) {
2506
- this._privOnInternalSelected.push(callback);
2530
+ this.privOnInternalSelected.push(callback);
2507
2531
  }
2508
2532
  /**
2509
2533
  * Registers a listener invoked when visibility changes (via setter `visible`).
@@ -2511,7 +2535,7 @@
2511
2535
  * @param {(evtToken: IEventCallback, model: OptionModel, visible: boolean) => void} callback - Visibility listener.
2512
2536
  */
2513
2537
  onVisibilityChanged(callback) {
2514
- this._privOnVisibilityChanged.push(callback);
2538
+ this.privOnVisibilityChanged.push(callback);
2515
2539
  }
2516
2540
  /**
2517
2541
  * Hook called when the target <option> element changes.
@@ -2552,11 +2576,12 @@
2552
2576
  * @param {object} options - Configuration object passed to GroupModel/OptionModel and view infrastructure.
2553
2577
  */
2554
2578
  constructor(options) {
2555
- this._privModelList = [];
2556
- this._privAdapterHandle = null;
2557
- this._privRecyclerViewHandle = null;
2558
- this._lastFingerprint = null;
2579
+ this.privModelList = [];
2580
+ this.privAdapterHandle = null;
2581
+ this.privRecyclerViewHandle = null;
2582
+ this.lastFingerprint = null;
2559
2583
  this.options = null;
2584
+ this.oldPosition = 0;
2560
2585
  this.options = options;
2561
2586
  }
2562
2587
  /**
@@ -2565,7 +2590,7 @@
2565
2590
  * @param {new TAdapter} adapter - The adapter constructor (class) to instantiate.
2566
2591
  */
2567
2592
  setupAdapter(adapter) {
2568
- this._privAdapter = adapter;
2593
+ this.privAdapter = adapter;
2569
2594
  }
2570
2595
  /**
2571
2596
  * Registers the RecyclerView class responsible for hosting and updating item views.
@@ -2573,7 +2598,7 @@
2573
2598
  * @param {new RecyclerViewContract<TAdapter>} recyclerView - The recycler view constructor.
2574
2599
  */
2575
2600
  setupRecyclerView(recyclerView) {
2576
- this._privRecyclerView = recyclerView;
2601
+ this.privRecyclerView = recyclerView;
2577
2602
  }
2578
2603
  /**
2579
2604
  * Checks whether the provided model data differs from the last recorded fingerprint.
@@ -2584,10 +2609,10 @@
2584
2609
  * @returns {boolean} True if there are real changes; false otherwise.
2585
2610
  */
2586
2611
  hasRealChanges(modelData) {
2587
- const newFingerprint = this._createFingerprint(modelData);
2588
- const hasChanges = newFingerprint !== this._lastFingerprint;
2612
+ const newFingerprint = this.createFingerprint(modelData);
2613
+ const hasChanges = newFingerprint !== this.lastFingerprint;
2589
2614
  if (hasChanges)
2590
- this._lastFingerprint = newFingerprint;
2615
+ this.lastFingerprint = newFingerprint;
2591
2616
  return hasChanges;
2592
2617
  }
2593
2618
  /**
@@ -2599,7 +2624,7 @@
2599
2624
  * @param {Array<HTMLOptionElement|HTMLOptGroupElement>} modelData - The current model data to fingerprint.
2600
2625
  * @returns {string} A deterministic fingerprint representing the structure and selection state.
2601
2626
  */
2602
- _createFingerprint(modelData) {
2627
+ createFingerprint(modelData) {
2603
2628
  return modelData
2604
2629
  .map((item) => {
2605
2630
  if (item.tagName === "OPTGROUP") {
@@ -2627,12 +2652,12 @@
2627
2652
  * @returns {Array<GroupModel|OptionModel>} - The ordered list of group and option models.
2628
2653
  */
2629
2654
  createModelResources(modelData) {
2630
- this._privModelList = [];
2655
+ this.privModelList = [];
2631
2656
  let currentGroup = null;
2632
2657
  modelData.forEach((data) => {
2633
2658
  if (data.tagName === "OPTGROUP") {
2634
2659
  currentGroup = new GroupModel(this.options, data);
2635
- this._privModelList.push(currentGroup);
2660
+ this.privModelList.push(currentGroup);
2636
2661
  }
2637
2662
  else if (data.tagName === "OPTION") {
2638
2663
  const optionEl = data;
@@ -2643,12 +2668,12 @@
2643
2668
  optionModel.group = currentGroup;
2644
2669
  }
2645
2670
  else {
2646
- this._privModelList.push(optionModel);
2671
+ this.privModelList.push(optionModel);
2647
2672
  currentGroup = null;
2648
2673
  }
2649
2674
  }
2650
2675
  });
2651
- return this._privModelList;
2676
+ return this.privModelList;
2652
2677
  }
2653
2678
  /**
2654
2679
  * Replaces the current model list with new data and syncs it into the adapter,
@@ -2656,12 +2681,12 @@
2656
2681
  *
2657
2682
  * @param {Array<HTMLOptGroupElement|HTMLOptionElement>} modelData - New source elements to rebuild models from.
2658
2683
  */
2659
- replace(modelData) {
2660
- this._lastFingerprint = null;
2684
+ async replace(modelData) {
2685
+ this.lastFingerprint = null;
2661
2686
  this.createModelResources(modelData);
2662
- if (this._privAdapterHandle) {
2687
+ if (this.privAdapterHandle) {
2663
2688
  // Adapter expects TModel[], but this manager's list is GroupModel|OptionModel.
2664
- this._privAdapterHandle.syncFromSource(this._privModelList);
2689
+ await this.privAdapterHandle.syncFromSource(this.privModelList);
2665
2690
  }
2666
2691
  this.refresh(false);
2667
2692
  }
@@ -2670,7 +2695,7 @@
2670
2695
  * typically used after external updates to model data.
2671
2696
  */
2672
2697
  notify() {
2673
- if (!this._privAdapterHandle)
2698
+ if (!this.privAdapterHandle)
2674
2699
  return;
2675
2700
  this.refresh(false);
2676
2701
  }
@@ -2679,11 +2704,11 @@
2679
2704
  * and applies optional configuration overrides for adapter and recyclerView.
2680
2705
  */
2681
2706
  load(viewElement, adapterOpt = {}, recyclerViewOpt = {}) {
2682
- this._privAdapterHandle = new this._privAdapter(this._privModelList);
2683
- Object.assign(this._privAdapterHandle, adapterOpt);
2684
- this._privRecyclerViewHandle = new this._privRecyclerView(viewElement);
2685
- Object.assign(this._privRecyclerViewHandle, recyclerViewOpt);
2686
- this._privRecyclerViewHandle.setAdapter(this._privAdapterHandle);
2707
+ this.privAdapterHandle = new this.privAdapter(this.privModelList);
2708
+ Object.assign(this.privAdapterHandle, adapterOpt);
2709
+ this.privRecyclerViewHandle = new this.privRecyclerView(viewElement);
2710
+ Object.assign(this.privRecyclerViewHandle, recyclerViewOpt);
2711
+ this.privRecyclerViewHandle.setAdapter(this.privAdapterHandle);
2687
2712
  }
2688
2713
  /**
2689
2714
  * Diffs existing models against new <optgroup>/<option> data to update in place:
@@ -2693,7 +2718,7 @@
2693
2718
  update(modelData) {
2694
2719
  if (!this.hasRealChanges(modelData))
2695
2720
  return;
2696
- const oldModels = this._privModelList;
2721
+ const oldModels = this.privModelList;
2697
2722
  const newModels = [];
2698
2723
  const oldGroupMap = new Map();
2699
2724
  const oldOptionMap = new Map();
@@ -2765,6 +2790,10 @@
2765
2790
  }
2766
2791
  });
2767
2792
  let isUpdate = true;
2793
+ if (this.oldPosition == 0) {
2794
+ isUpdate = false;
2795
+ }
2796
+ this.oldPosition = position;
2768
2797
  oldGroupMap.forEach((removedGroup) => {
2769
2798
  isUpdate = false;
2770
2799
  removedGroup.remove();
@@ -2773,11 +2802,11 @@
2773
2802
  isUpdate = false;
2774
2803
  removedOption.remove();
2775
2804
  });
2776
- this._privModelList = newModels;
2777
- if (this._privAdapterHandle) {
2778
- this._privAdapterHandle.updateData(this._privModelList);
2805
+ this.privModelList = newModels;
2806
+ if (this.privAdapterHandle) {
2807
+ this.privAdapterHandle.updateData(this.privModelList);
2779
2808
  }
2780
- this.onUpdated();
2809
+ // this.onUpdated();
2781
2810
  this.refresh(isUpdate);
2782
2811
  }
2783
2812
  /**
@@ -2791,8 +2820,8 @@
2791
2820
  * @param {boolean} value - True to skip events; false to restore normal behavior.
2792
2821
  */
2793
2822
  skipEvent(value) {
2794
- if (this._privAdapterHandle)
2795
- this._privAdapterHandle.isSkipEvent = value;
2823
+ if (this.privAdapterHandle)
2824
+ this.privAdapterHandle.isSkipEvent = value;
2796
2825
  }
2797
2826
  /**
2798
2827
  * Re-renders the recycler view if present and invokes the post-refresh hook.
@@ -2801,9 +2830,9 @@
2801
2830
  * @param isUpdate - Indicates if this refresh is due to an update operation.
2802
2831
  */
2803
2832
  refresh(isUpdate) {
2804
- if (!this._privRecyclerViewHandle)
2833
+ if (!this.privRecyclerViewHandle)
2805
2834
  return;
2806
- this._privRecyclerViewHandle.refresh(isUpdate);
2835
+ this.privRecyclerViewHandle.refresh(isUpdate);
2807
2836
  this.onUpdated();
2808
2837
  }
2809
2838
  /**
@@ -2812,9 +2841,9 @@
2812
2841
  */
2813
2842
  getResources() {
2814
2843
  return {
2815
- modelList: this._privModelList,
2816
- adapter: this._privAdapterHandle,
2817
- recyclerView: this._privRecyclerViewHandle,
2844
+ modelList: this.privModelList,
2845
+ adapter: this.privAdapterHandle,
2846
+ recyclerView: this.privRecyclerViewHandle,
2818
2847
  };
2819
2848
  }
2820
2849
  /**
@@ -2822,14 +2851,14 @@
2822
2851
  * enabling observers to react before a change is applied.
2823
2852
  */
2824
2853
  triggerChanging(event_name) {
2825
- this._privAdapterHandle?.changingProp(event_name);
2854
+ return this.privAdapterHandle?.changingProp(event_name);
2826
2855
  }
2827
2856
  /**
2828
2857
  * Triggers the adapter's post-change pipeline for a named event,
2829
2858
  * notifying observers after a change has been applied.
2830
2859
  */
2831
2860
  triggerChanged(event_name) {
2832
- this._privAdapterHandle?.changeProp(event_name);
2861
+ return this.privAdapterHandle?.changeProp(event_name);
2833
2862
  }
2834
2863
  }
2835
2864
 
@@ -2920,6 +2949,7 @@
2920
2949
  this.selectUIMask = null;
2921
2950
  this.parentMask = null;
2922
2951
  this.modelManager = null;
2952
+ this.modelDatas = [];
2923
2953
  if (options)
2924
2954
  this.init(options);
2925
2955
  }
@@ -2959,7 +2989,10 @@
2959
2989
  * Keeps the accessory box aligned relative to the parent mask.
2960
2990
  */
2961
2991
  refreshLocation() {
2962
- if (!this.parentMask || !this.node || !this.selectUIMask || !this.options)
2992
+ if (!this.parentMask ||
2993
+ !this.node ||
2994
+ !this.selectUIMask ||
2995
+ !this.options)
2963
2996
  return;
2964
2997
  const ref = this.options.accessoryStyle === "top"
2965
2998
  ? this.selectUIMask
@@ -2985,7 +3018,6 @@
2985
3018
  return;
2986
3019
  this.node.replaceChildren();
2987
3020
  if (modelDatas.length > 0 && this.options.multiple) {
2988
- this.node.classList.remove("hide");
2989
3021
  modelDatas.forEach((modelData) => {
2990
3022
  Libs.mountNode({
2991
3023
  AccessoryItem: {
@@ -2998,12 +3030,10 @@
2998
3030
  role: "button",
2999
3031
  ariaLabel: `${this.options.textAccessoryDeselect}${modelData.textContent}`,
3000
3032
  title: `${this.options.textAccessoryDeselect}${modelData.textContent}`,
3001
- onclick: (evt) => {
3033
+ onclick: async (evt) => {
3002
3034
  evt.preventDefault();
3003
- this.modelManager?.triggerChanging?.("select");
3004
- setTimeout(() => {
3005
- modelData.selected = false;
3006
- }, 10);
3035
+ await this.modelManager?.triggerChanging?.("select");
3036
+ modelData.selected = false;
3007
3037
  },
3008
3038
  },
3009
3039
  },
@@ -3020,10 +3050,26 @@
3020
3050
  });
3021
3051
  }
3022
3052
  else {
3023
- this.node.classList.add("hide");
3053
+ modelDatas = [];
3024
3054
  }
3055
+ this.modelDatas = modelDatas;
3056
+ this.refreshDisplay();
3025
3057
  iEvents.trigger(window, "resize");
3026
3058
  }
3059
+ refreshDisplay() {
3060
+ if (this.options?.accessoryVisible && this.modelDatas.length > 0 && this.options.multiple) {
3061
+ this.show();
3062
+ }
3063
+ else {
3064
+ this.hide();
3065
+ }
3066
+ }
3067
+ show() {
3068
+ this.node.classList.remove("hide");
3069
+ }
3070
+ hide() {
3071
+ this.node.classList.add("hide");
3072
+ }
3027
3073
  }
3028
3074
 
3029
3075
  class SearchController {
@@ -3036,11 +3082,11 @@
3036
3082
  * @param {SelectBox} selectBox - SelectBox handle.
3037
3083
  */
3038
3084
  constructor(selectElement, modelManager, selectBox) {
3039
- this._ajaxConfig = null;
3040
- this._abortController = null;
3041
- this._popup = null;
3042
- this._selectBox = null;
3043
- this._paginationState = {
3085
+ this.ajaxConfig = null;
3086
+ this.abortController = null;
3087
+ this.popup = null;
3088
+ this.selectBox = null;
3089
+ this.paginationState = {
3044
3090
  currentPage: 0,
3045
3091
  totalPages: 1,
3046
3092
  hasMore: false,
@@ -3048,9 +3094,9 @@
3048
3094
  currentKeyword: "",
3049
3095
  isPaginationEnabled: false,
3050
3096
  };
3051
- this._select = selectElement;
3052
- this._modelManager = modelManager;
3053
- this._selectBox = selectBox;
3097
+ this.select = selectElement;
3098
+ this.modelManager = modelManager;
3099
+ this.selectBox = selectBox;
3054
3100
  }
3055
3101
  /**
3056
3102
  * Indicates whether AJAX-based search is configured.
@@ -3058,7 +3104,7 @@
3058
3104
  * @returns {boolean} - True if AJAX config is present; false otherwise.
3059
3105
  */
3060
3106
  isAjax() {
3061
- return !!this._ajaxConfig;
3107
+ return !!this.ajaxConfig;
3062
3108
  }
3063
3109
  /**
3064
3110
  * Load specific options by their values from server
@@ -3066,14 +3112,14 @@
3066
3112
  * @returns {Promise<{success: boolean, items: Array, message?: string}>}
3067
3113
  */
3068
3114
  async loadByValues(values) {
3069
- if (!this._ajaxConfig) {
3115
+ if (!this.ajaxConfig) {
3070
3116
  return { success: false, items: [], message: "Ajax not configured" };
3071
3117
  }
3072
3118
  const valuesArray = Array.isArray(values) ? values : [values];
3073
3119
  if (valuesArray.length === 0)
3074
3120
  return { success: true, items: [] };
3075
3121
  try {
3076
- const cfg = this._ajaxConfig;
3122
+ const cfg = this.ajaxConfig;
3077
3123
  let payload;
3078
3124
  if (typeof cfg.dataByValues === "function") {
3079
3125
  payload = cfg.dataByValues(valuesArray);
@@ -3082,7 +3128,7 @@
3082
3128
  payload = {
3083
3129
  values: valuesArray.join(","),
3084
3130
  load_by_values: "1",
3085
- ...(typeof cfg.data === "function" ? cfg.data.bind(this._selectBox.Selective.find(this._selectBox.container.targetElement))("", 0) : cfg.data ?? {}),
3131
+ ...(typeof cfg.data === "function" ? cfg.data.bind(this.selectBox.Selective.find(this.selectBox.container.targetElement))("", 0) : cfg.data ?? {}),
3086
3132
  };
3087
3133
  }
3088
3134
  let response;
@@ -3102,7 +3148,7 @@
3102
3148
  if (!response.ok)
3103
3149
  throw new Error(`HTTP error! status: ${response.status}`);
3104
3150
  const data = await response.json();
3105
- const result = this._parseResponse(data);
3151
+ const result = this.parseResponse(data);
3106
3152
  return { success: true, items: result.items };
3107
3153
  }
3108
3154
  catch (error) {
@@ -3116,7 +3162,7 @@
3116
3162
  * @returns {{existing: string[], missing: string[]}}
3117
3163
  */
3118
3164
  checkMissingValues(values) {
3119
- const allOptions = Array.from(this._select.options);
3165
+ const allOptions = Array.from(this.select.options);
3120
3166
  const existingValues = allOptions.map((opt) => opt.value);
3121
3167
  const existing = values.filter((v) => existingValues.includes(v));
3122
3168
  const missing = values.filter((v) => !existingValues.includes(v));
@@ -3128,7 +3174,7 @@
3128
3174
  * @param {object} config - AJAX configuration object (e.g., endpoint, headers, query params).
3129
3175
  */
3130
3176
  setAjax(config) {
3131
- this._ajaxConfig = config;
3177
+ this.ajaxConfig = config;
3132
3178
  }
3133
3179
  /**
3134
3180
  * Attaches a Popup instance to allow UI updates during search (e.g., loading, resize).
@@ -3136,34 +3182,34 @@
3136
3182
  * @param {Popup} popupInstance - The popup used to display search results and loading state.
3137
3183
  */
3138
3184
  setPopup(popupInstance) {
3139
- this._popup = popupInstance;
3185
+ this.popup = popupInstance;
3140
3186
  }
3141
3187
  /**
3142
3188
  * Returns a shallow copy of the current pagination state used for search/infinite scroll.
3143
3189
  */
3144
3190
  getPaginationState() {
3145
- return { ...this._paginationState };
3191
+ return { ...this.paginationState };
3146
3192
  }
3147
3193
  /**
3148
3194
  * Resets pagination counters while preserving whether pagination is enabled.
3149
3195
  * Clears page, totals, loading flags, and current keyword.
3150
3196
  */
3151
3197
  resetPagination() {
3152
- this._paginationState = {
3198
+ this.paginationState = {
3153
3199
  currentPage: 0,
3154
3200
  totalPages: 1,
3155
3201
  hasMore: false,
3156
3202
  isLoading: false,
3157
3203
  currentKeyword: "",
3158
- isPaginationEnabled: this._paginationState.isPaginationEnabled,
3204
+ isPaginationEnabled: this.paginationState.isPaginationEnabled,
3159
3205
  };
3160
3206
  }
3161
3207
  /**
3162
3208
  * Clears the current keyword and makes all options visible (local reset).
3163
3209
  */
3164
3210
  clear() {
3165
- this._paginationState.currentKeyword = "";
3166
- const { modelList } = this._modelManager.getResources();
3211
+ this.paginationState.currentKeyword = "";
3212
+ const { modelList } = this.modelManager.getResources();
3167
3213
  const flatOptions = [];
3168
3214
  for (const m of modelList) {
3169
3215
  if (m instanceof OptionModel)
@@ -3179,7 +3225,7 @@
3179
3225
  * Performs a search with either AJAX or local filtering depending on configuration.
3180
3226
  */
3181
3227
  async search(keyword, append = false) {
3182
- if (this._ajaxConfig)
3228
+ if (this.ajaxConfig)
3183
3229
  return this._ajaxSearch(keyword, append);
3184
3230
  return this._localSearch(keyword);
3185
3231
  }
@@ -3187,16 +3233,16 @@
3187
3233
  * Loads the next page for AJAX pagination if enabled and not already loading.
3188
3234
  */
3189
3235
  async loadMore() {
3190
- if (!this._ajaxConfig)
3236
+ if (!this.ajaxConfig)
3191
3237
  return { success: false, message: "Ajax not enabled" };
3192
- if (this._paginationState.isLoading)
3238
+ if (this.paginationState.isLoading)
3193
3239
  return { success: false, message: "Already loading" };
3194
- if (!this._paginationState.isPaginationEnabled)
3240
+ if (!this.paginationState.isPaginationEnabled)
3195
3241
  return { success: false, message: "Pagination not enabled" };
3196
- if (!this._paginationState.hasMore)
3242
+ if (!this.paginationState.hasMore)
3197
3243
  return { success: false, message: "No more data" };
3198
- this._paginationState.currentPage++;
3199
- return this._ajaxSearch(this._paginationState.currentKeyword, true);
3244
+ this.paginationState.currentPage++;
3245
+ return this._ajaxSearch(this.paginationState.currentKeyword, true);
3200
3246
  }
3201
3247
  /**
3202
3248
  * Executes a local (in-memory) search by normalizing the keyword (lowercase, non-accent)
@@ -3204,10 +3250,10 @@
3204
3250
  */
3205
3251
  async _localSearch(keyword) {
3206
3252
  if (this.compareSearchTrigger(keyword))
3207
- this._paginationState.currentKeyword = keyword;
3253
+ this.paginationState.currentKeyword = keyword;
3208
3254
  const lower = String(keyword ?? "").toLowerCase();
3209
3255
  const lowerNA = Libs.string2normalize(lower);
3210
- const { modelList } = this._modelManager.getResources();
3256
+ const { modelList } = this.modelManager.getResources();
3211
3257
  const flatOptions = [];
3212
3258
  for (const m of modelList) {
3213
3259
  if (m instanceof OptionModel)
@@ -3233,29 +3279,29 @@
3233
3279
  * to determine if a new search should be triggered.
3234
3280
  */
3235
3281
  compareSearchTrigger(keyword) {
3236
- return keyword !== this._paginationState.currentKeyword;
3282
+ return keyword !== this.paginationState.currentKeyword;
3237
3283
  }
3238
3284
  /**
3239
3285
  * Executes an AJAX-based search with optional appending.
3240
3286
  */
3241
3287
  async _ajaxSearch(keyword, append = false) {
3242
- const cfg = this._ajaxConfig;
3288
+ const cfg = this.ajaxConfig;
3243
3289
  if (this.compareSearchTrigger(keyword)) {
3244
3290
  this.resetPagination();
3245
- this._paginationState.currentKeyword = keyword;
3291
+ this.paginationState.currentKeyword = keyword;
3246
3292
  append = false;
3247
3293
  }
3248
- this._paginationState.isLoading = true;
3249
- this._popup?.showLoading();
3250
- this._abortController?.abort();
3251
- this._abortController = new AbortController();
3252
- const page = this._paginationState.currentPage;
3253
- const selectedValues = Array.from(this._select.selectedOptions)
3294
+ this.paginationState.isLoading = true;
3295
+ this.popup?.showLoading();
3296
+ this.abortController?.abort();
3297
+ this.abortController = new AbortController();
3298
+ const page = this.paginationState.currentPage;
3299
+ const selectedValues = Array.from(this.select.selectedOptions)
3254
3300
  .map((opt) => opt.value)
3255
3301
  .join(",");
3256
3302
  let payload;
3257
3303
  if (typeof cfg.data === "function") {
3258
- payload = cfg.data.bind(this._selectBox.Selective.find(this._selectBox.container.targetElement))(keyword, page);
3304
+ payload = cfg.data.bind(this.selectBox.Selective.find(this.selectBox.container.targetElement))(keyword, page);
3259
3305
  if (payload && typeof payload.selectedValue === "undefined")
3260
3306
  payload.selectedValue = selectedValues;
3261
3307
  }
@@ -3271,27 +3317,27 @@
3271
3317
  method: "POST",
3272
3318
  body: formData,
3273
3319
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
3274
- signal: this._abortController.signal,
3320
+ signal: this.abortController.signal,
3275
3321
  });
3276
3322
  }
3277
3323
  else {
3278
3324
  const params = new URLSearchParams(payload).toString();
3279
- response = await fetch(`${cfg.url}?${params}`, { signal: this._abortController.signal });
3325
+ response = await fetch(`${cfg.url}?${params}`, { signal: this.abortController.signal });
3280
3326
  }
3281
3327
  const data = await response.json();
3282
- const result = this._parseResponse(data);
3328
+ const result = this.parseResponse(data);
3283
3329
  if (result.hasPagination) {
3284
- this._paginationState.isPaginationEnabled = true;
3285
- this._paginationState.currentPage = result.page;
3286
- this._paginationState.totalPages = result.totalPages;
3287
- this._paginationState.hasMore = result.hasMore;
3330
+ this.paginationState.isPaginationEnabled = true;
3331
+ this.paginationState.currentPage = result.page;
3332
+ this.paginationState.totalPages = result.totalPages;
3333
+ this.paginationState.hasMore = result.hasMore;
3288
3334
  }
3289
3335
  else {
3290
- this._paginationState.isPaginationEnabled = false;
3336
+ this.paginationState.isPaginationEnabled = false;
3291
3337
  }
3292
3338
  this.applyAjaxResult(result.items, !!cfg.keepSelected, append);
3293
- this._paginationState.isLoading = false;
3294
- this._popup?.hideLoading();
3339
+ this.paginationState.isLoading = false;
3340
+ this.popup?.hideLoading();
3295
3341
  return {
3296
3342
  success: true,
3297
3343
  hasResults: result.items.length > 0,
@@ -3303,8 +3349,8 @@
3303
3349
  };
3304
3350
  }
3305
3351
  catch (error) {
3306
- this._paginationState.isLoading = false;
3307
- this._popup?.hideLoading();
3352
+ this.paginationState.isLoading = false;
3353
+ this.popup?.hideLoading();
3308
3354
  if (error?.name === "AbortError")
3309
3355
  return { success: false, message: "Request aborted" };
3310
3356
  console.error("Ajax search error:", error);
@@ -3314,7 +3360,7 @@
3314
3360
  /**
3315
3361
  * Parses various server response shapes into a normalized structure for options and groups.
3316
3362
  */
3317
- _parseResponse(data) {
3363
+ parseResponse(data) {
3318
3364
  let items = [];
3319
3365
  let hasPagination = false;
3320
3366
  let page = 0;
@@ -3381,7 +3427,7 @@
3381
3427
  * Applies normalized AJAX results to the underlying <select> element.
3382
3428
  */
3383
3429
  applyAjaxResult(items, keepSelected, append = false) {
3384
- const select = this._select;
3430
+ const select = this.select;
3385
3431
  let oldSelected = [];
3386
3432
  if (keepSelected)
3387
3433
  oldSelected = Array.from(select.selectedOptions).map((o) => o.value);
@@ -3436,7 +3482,6 @@
3436
3482
  select.appendChild(option);
3437
3483
  }
3438
3484
  });
3439
- select.dispatchEvent(new CustomEvent("options:changed"));
3440
3485
  }
3441
3486
  }
3442
3487
 
@@ -3446,29 +3491,22 @@
3446
3491
  class SelectObserver {
3447
3492
  /**
3448
3493
  * Initializes the SelectObserver for a given <select> element.
3449
- * Captures the initial snapshot, sets up a MutationObserver, and listens for custom "options:changed" events.
3494
+ * Captures the initial snapshot, sets up a MutationObserver.
3450
3495
  * Changes are debounced to prevent excessive calls.
3451
3496
  *
3452
3497
  * @param {HTMLSelectElement} select - The <select> element to observe.
3453
3498
  */
3454
3499
  constructor(select) {
3455
- this._debounceTimer = null;
3456
- this._lastSnapshot = null;
3500
+ this.debounceTimer = null;
3501
+ this.lastSnapshot = null;
3457
3502
  this._DEBOUNCE_DELAY = 50;
3458
- this._select = select;
3459
- this._lastSnapshot = this._createSnapshot();
3460
- this._observer = new MutationObserver(() => {
3461
- if (this._debounceTimer)
3462
- clearTimeout(this._debounceTimer);
3463
- this._debounceTimer = setTimeout(() => {
3464
- this._handleChange();
3465
- }, this._DEBOUNCE_DELAY);
3466
- });
3467
- select.addEventListener("options:changed", () => {
3468
- if (this._debounceTimer)
3469
- clearTimeout(this._debounceTimer);
3470
- this._debounceTimer = setTimeout(() => {
3471
- this._handleChange();
3503
+ this.select = select;
3504
+ this.lastSnapshot = this.createSnapshot();
3505
+ this.observer = new MutationObserver(() => {
3506
+ if (this.debounceTimer)
3507
+ clearTimeout(this.debounceTimer);
3508
+ this.debounceTimer = setTimeout(() => {
3509
+ this.handleChange();
3472
3510
  }, this._DEBOUNCE_DELAY);
3473
3511
  });
3474
3512
  }
@@ -3478,8 +3516,8 @@
3478
3516
  *
3479
3517
  * @returns {SelectSnapshot} A snapshot of the options state.
3480
3518
  */
3481
- _createSnapshot() {
3482
- const options = Array.from(this._select.options);
3519
+ createSnapshot() {
3520
+ const options = Array.from(this.select.options);
3483
3521
  return {
3484
3522
  length: options.length,
3485
3523
  values: options.map((opt) => opt.value).join(","),
@@ -3493,28 +3531,28 @@
3493
3531
  *
3494
3532
  * @returns {boolean} True if a real change occurred, otherwise false.
3495
3533
  */
3496
- _hasRealChange() {
3497
- const newSnapshot = this._createSnapshot();
3498
- const changed = JSON.stringify(newSnapshot) !== JSON.stringify(this._lastSnapshot);
3534
+ hasRealChange() {
3535
+ const newSnapshot = this.createSnapshot();
3536
+ const changed = JSON.stringify(newSnapshot) !== JSON.stringify(this.lastSnapshot);
3499
3537
  if (changed)
3500
- this._lastSnapshot = newSnapshot;
3538
+ this.lastSnapshot = newSnapshot;
3501
3539
  return changed;
3502
3540
  }
3503
3541
  /**
3504
3542
  * Handles detected changes after debouncing.
3505
3543
  * If a real change is found, invokes the onChanged() hook with the current <select> element.
3506
3544
  */
3507
- _handleChange() {
3508
- if (!this._hasRealChange())
3545
+ handleChange() {
3546
+ if (!this.hasRealChange())
3509
3547
  return;
3510
- this.onChanged(this._select);
3548
+ this.onChanged(this.select);
3511
3549
  }
3512
3550
  /**
3513
3551
  * Starts observing the <select> element for child list mutations and attribute changes.
3514
3552
  * Uses MutationObserver with a debounce mechanism to batch rapid updates.
3515
3553
  */
3516
3554
  connect() {
3517
- this._observer.observe(this._select, {
3555
+ this.observer.observe(this.select, {
3518
3556
  childList: true,
3519
3557
  subtree: false,
3520
3558
  attributes: true,
@@ -3536,10 +3574,10 @@
3536
3574
  * Ensures no further change handling occurs after disconnecting.
3537
3575
  */
3538
3576
  disconnect() {
3539
- if (this._debounceTimer)
3540
- clearTimeout(this._debounceTimer);
3541
- this._debounceTimer = null;
3542
- this._observer.disconnect();
3577
+ if (this.debounceTimer)
3578
+ clearTimeout(this.debounceTimer);
3579
+ this.debounceTimer = null;
3580
+ this.observer.disconnect();
3543
3581
  }
3544
3582
  }
3545
3583
 
@@ -3554,9 +3592,9 @@
3554
3592
  * @param {HTMLElement} element - The element whose dataset (data-* attributes) will be observed.
3555
3593
  */
3556
3594
  constructor(element) {
3557
- this._debounceTimer = null;
3558
- this._element = element;
3559
- this._observer = new MutationObserver((mutations) => {
3595
+ this.debounceTimer = null;
3596
+ this.element = element;
3597
+ this.observer = new MutationObserver((mutations) => {
3560
3598
  let datasetChanged = false;
3561
3599
  for (const mutation of mutations) {
3562
3600
  if (mutation.type === "attributes" && mutation.attributeName?.startsWith("data-")) {
@@ -3566,14 +3604,14 @@
3566
3604
  }
3567
3605
  if (!datasetChanged)
3568
3606
  return;
3569
- if (this._debounceTimer)
3570
- clearTimeout(this._debounceTimer);
3571
- this._debounceTimer = setTimeout(() => {
3572
- this.onChanged({ ...this._element.dataset });
3607
+ if (this.debounceTimer)
3608
+ clearTimeout(this.debounceTimer);
3609
+ this.debounceTimer = setTimeout(() => {
3610
+ this.onChanged({ ...this.element.dataset });
3573
3611
  }, 50);
3574
3612
  });
3575
3613
  element.addEventListener("dataset:changed", () => {
3576
- this.onChanged({ ...this._element.dataset });
3614
+ this.onChanged({ ...this.element.dataset });
3577
3615
  });
3578
3616
  }
3579
3617
  /**
@@ -3581,7 +3619,7 @@
3581
3619
  * Uses MutationObserver to track updates to data-* attributes.
3582
3620
  */
3583
3621
  connect() {
3584
- this._observer.observe(this._element, {
3622
+ this.observer.observe(this.element, {
3585
3623
  attributes: true,
3586
3624
  attributeOldValue: true,
3587
3625
  });
@@ -3600,10 +3638,10 @@
3600
3638
  * Stops observing the element and clears any pending debounce timers.
3601
3639
  */
3602
3640
  disconnect() {
3603
- if (this._debounceTimer)
3604
- clearTimeout(this._debounceTimer);
3605
- this._debounceTimer = null;
3606
- this._observer.disconnect();
3641
+ if (this.debounceTimer)
3642
+ clearTimeout(this.debounceTimer);
3643
+ this.debounceTimer = null;
3644
+ this.observer.disconnect();
3607
3645
  }
3608
3646
  }
3609
3647
 
@@ -3656,7 +3694,7 @@
3656
3694
  * @param {Function} callback - Function to execute before the property changes.
3657
3695
  */
3658
3696
  onPropChanging(propName, callback) {
3659
- Libs.callbackScheduler.on(`${propName}ing_${this.adapterKey}`, callback, { debounce: 1 });
3697
+ Libs.callbackScheduler.on(`${propName}ing_${this.adapterKey}`, callback, { debounce: 0 });
3660
3698
  }
3661
3699
  /**
3662
3700
  * Registers a post-change callback for a property change pipeline.
@@ -3666,7 +3704,7 @@
3666
3704
  * @param {Function} callback - Function to execute after the property changes.
3667
3705
  */
3668
3706
  onPropChanged(propName, callback) {
3669
- Libs.callbackScheduler.on(`${propName}_${this.adapterKey}`, callback);
3707
+ Libs.callbackScheduler.on(`${propName}_${this.adapterKey}`, callback, { debounce: 0 });
3670
3708
  }
3671
3709
  /**
3672
3710
  * Triggers the post-change pipeline for a given property, passing optional parameters
@@ -3676,7 +3714,7 @@
3676
3714
  * @param {...any} params - Parameters forwarded to the callbacks.
3677
3715
  */
3678
3716
  changeProp(propName, ...params) {
3679
- Libs.callbackScheduler.run(`${propName}_${this.adapterKey}`, ...params);
3717
+ return Libs.callbackScheduler.run(`${propName}_${this.adapterKey}`, ...params);
3680
3718
  }
3681
3719
  /**
3682
3720
  * Triggers the pre-change pipeline for a given property, passing optional parameters
@@ -3686,7 +3724,7 @@
3686
3724
  * @param {...any} params - Parameters forwarded to the callbacks.
3687
3725
  */
3688
3726
  changingProp(propName, ...params) {
3689
- Libs.callbackScheduler.run(`${propName}ing_${this.adapterKey}`, ...params);
3727
+ return Libs.callbackScheduler.run(`${propName}ing_${this.adapterKey}`, ...params);
3690
3728
  }
3691
3729
  /**
3692
3730
  * Creates and returns a viewer instance for the given item within the specified parent container.
@@ -3713,10 +3751,10 @@
3713
3751
  *
3714
3752
  * @param {TItem[]} items - The new list of items to set.
3715
3753
  */
3716
- setItems(items) {
3717
- this.changingProp("items", items);
3754
+ async setItems(items) {
3755
+ await this.changingProp("items", items);
3718
3756
  this.items = items;
3719
- this.changeProp("items", items);
3757
+ await this.changeProp("items", items);
3720
3758
  }
3721
3759
  /**
3722
3760
  * Synchronizes adapter items from an external source by delegating to setItems().
@@ -3724,8 +3762,8 @@
3724
3762
  *
3725
3763
  * @param {TItem[]} items - The source list of items to synchronize.
3726
3764
  */
3727
- syncFromSource(items) {
3728
- this.setItems(items);
3765
+ async syncFromSource(items) {
3766
+ await this.setItems(items);
3729
3767
  }
3730
3768
  /**
3731
3769
  * Iterates through all items and ensures each has a viewer. For new items, calls viewHolder()
@@ -3903,10 +3941,10 @@
3903
3941
  constructor(parent) {
3904
3942
  super(parent);
3905
3943
  this.view = null;
3906
- this._config = null;
3907
- this._configProxy = null;
3908
- this._isRendered = false;
3909
- this._setupConfigProxy();
3944
+ this.config = null;
3945
+ this.configProxy = null;
3946
+ this.isRendered = false;
3947
+ this.setupConfigProxy();
3910
3948
  }
3911
3949
  /**
3912
3950
  * Creates the internal configuration object and wraps it with a Proxy.
@@ -3914,9 +3952,9 @@
3914
3952
  * applies only the necessary DOM changes for the updated property.
3915
3953
  * No DOM mutations occur before the first render.
3916
3954
  */
3917
- _setupConfigProxy() {
3955
+ setupConfigProxy() {
3918
3956
  const self = this;
3919
- this._config = {
3957
+ this.config = {
3920
3958
  isMultiple: false,
3921
3959
  hasImage: false,
3922
3960
  imagePosition: "right",
@@ -3926,7 +3964,7 @@
3926
3964
  labelValign: "center",
3927
3965
  labelHalign: "left",
3928
3966
  };
3929
- this._configProxy = new Proxy(this._config, {
3967
+ this.configProxy = new Proxy(this.config, {
3930
3968
  set(target, prop, value) {
3931
3969
  if (typeof prop !== "string")
3932
3970
  return true;
@@ -3934,8 +3972,8 @@
3934
3972
  const oldValue = target[key];
3935
3973
  if (oldValue !== value) {
3936
3974
  target[key] = value;
3937
- if (self._isRendered) {
3938
- self._applyPartialChange(key, value, oldValue);
3975
+ if (self.isRendered) {
3976
+ self.applyPartialChange(key, value, oldValue);
3939
3977
  }
3940
3978
  }
3941
3979
  return true;
@@ -3948,7 +3986,7 @@
3948
3986
  * @returns {boolean} True if multiple selection is enabled; otherwise false.
3949
3987
  */
3950
3988
  get isMultiple() {
3951
- return this._config.isMultiple;
3989
+ return this.config.isMultiple;
3952
3990
  }
3953
3991
  /**
3954
3992
  * Enables or disables multiple selection mode.
@@ -3957,7 +3995,7 @@
3957
3995
  * @param {boolean} value - True to enable multiple selection; false for single selection.
3958
3996
  */
3959
3997
  set isMultiple(value) {
3960
- this._configProxy.isMultiple = !!value;
3998
+ this.configProxy.isMultiple = !!value;
3961
3999
  }
3962
4000
  /**
3963
4001
  * Indicates whether the option includes an image block alongside the label.
@@ -3965,7 +4003,7 @@
3965
4003
  * @returns {boolean} True if an image is displayed; otherwise false.
3966
4004
  */
3967
4005
  get hasImage() {
3968
- return this._config.hasImage;
4006
+ return this.config.hasImage;
3969
4007
  }
3970
4008
  /**
3971
4009
  * Shows or hides the image block for the option.
@@ -3974,7 +4012,7 @@
3974
4012
  * @param {boolean} value - True to show the image; false to hide it.
3975
4013
  */
3976
4014
  set hasImage(value) {
3977
- this._configProxy.hasImage = !!value;
4015
+ this.configProxy.hasImage = !!value;
3978
4016
  }
3979
4017
  /**
3980
4018
  * Provides reactive access to the entire option configuration via a Proxy.
@@ -3983,7 +4021,7 @@
3983
4021
  * @returns {object} The proxied configuration object.
3984
4022
  */
3985
4023
  get optionConfig() {
3986
- return this._configProxy;
4024
+ return this.configProxy;
3987
4025
  }
3988
4026
  /**
3989
4027
  * Applies a set of configuration changes in batch.
@@ -3991,23 +4029,23 @@
3991
4029
  * When rendered, each changed property triggers a targeted DOM update via the proxy.
3992
4030
  */
3993
4031
  set optionConfig(config) {
3994
- if (!config || !this._configProxy || !this._config)
4032
+ if (!config || !this.configProxy || !this.config)
3995
4033
  return;
3996
4034
  const changes = {};
3997
- if (config.imageWidth !== undefined && config.imageWidth !== this._config.imageWidth)
4035
+ if (config.imageWidth !== undefined && config.imageWidth !== this.config.imageWidth)
3998
4036
  changes.imageWidth = config.imageWidth;
3999
- if (config.imageHeight !== undefined && config.imageHeight !== this._config.imageHeight)
4037
+ if (config.imageHeight !== undefined && config.imageHeight !== this.config.imageHeight)
4000
4038
  changes.imageHeight = config.imageHeight;
4001
- if (config.imageBorderRadius !== undefined && config.imageBorderRadius !== this._config.imageBorderRadius)
4039
+ if (config.imageBorderRadius !== undefined && config.imageBorderRadius !== this.config.imageBorderRadius)
4002
4040
  changes.imageBorderRadius = config.imageBorderRadius;
4003
- if (config.imagePosition !== undefined && config.imagePosition !== this._config.imagePosition)
4041
+ if (config.imagePosition !== undefined && config.imagePosition !== this.config.imagePosition)
4004
4042
  changes.imagePosition = config.imagePosition;
4005
- if (config.labelValign !== undefined && config.labelValign !== this._config.labelValign)
4043
+ if (config.labelValign !== undefined && config.labelValign !== this.config.labelValign)
4006
4044
  changes.labelValign = config.labelValign;
4007
- if (config.labelHalign !== undefined && config.labelHalign !== this._config.labelHalign)
4045
+ if (config.labelHalign !== undefined && config.labelHalign !== this.config.labelHalign)
4008
4046
  changes.labelHalign = config.labelHalign;
4009
4047
  if (Object.keys(changes).length > 0)
4010
- Object.assign(this._configProxy, changes);
4048
+ Object.assign(this.configProxy, changes);
4011
4049
  }
4012
4050
  /**
4013
4051
  * Renders the option view into the parent element.
@@ -4019,30 +4057,30 @@
4019
4057
  const viewClass = ["selective-ui-option-view"];
4020
4058
  const opt_id = Libs.randomString(7);
4021
4059
  const inputID = `option_${opt_id}`;
4022
- if (this._config.isMultiple)
4060
+ if (this.config.isMultiple)
4023
4061
  viewClass.push("multiple");
4024
- if (this._config.hasImage) {
4062
+ if (this.config.hasImage) {
4025
4063
  viewClass.push("has-image");
4026
- viewClass.push(`image-${this._config.imagePosition}`);
4064
+ viewClass.push(`image-${this.config.imagePosition}`);
4027
4065
  }
4028
4066
  const childStructure = {
4029
4067
  OptionInput: {
4030
4068
  tag: {
4031
4069
  node: "input",
4032
- type: this._config.isMultiple ? "checkbox" : "radio",
4070
+ type: this.config.isMultiple ? "checkbox" : "radio",
4033
4071
  classList: "allow-choice",
4034
4072
  id: inputID,
4035
4073
  },
4036
4074
  },
4037
- ...(this._config.hasImage && {
4075
+ ...(this.config.hasImage && {
4038
4076
  OptionImage: {
4039
4077
  tag: {
4040
4078
  node: "img",
4041
4079
  classList: "option-image",
4042
4080
  style: {
4043
- width: this._config.imageWidth,
4044
- height: this._config.imageHeight,
4045
- borderRadius: this._config.imageBorderRadius,
4081
+ width: this.config.imageWidth,
4082
+ height: this.config.imageHeight,
4083
+ borderRadius: this.config.imageBorderRadius,
4046
4084
  },
4047
4085
  },
4048
4086
  },
@@ -4052,8 +4090,8 @@
4052
4090
  node: "label",
4053
4091
  htmlFor: inputID,
4054
4092
  classList: [
4055
- `align-vertical-${this._config.labelValign}`,
4056
- `align-horizontal-${this._config.labelHalign}`,
4093
+ `align-vertical-${this.config.labelValign}`,
4094
+ `align-horizontal-${this.config.labelHalign}`,
4057
4095
  ],
4058
4096
  },
4059
4097
  child: {
@@ -4075,13 +4113,13 @@
4075
4113
  },
4076
4114
  });
4077
4115
  this.parent.appendChild(this.view.view);
4078
- this._isRendered = true;
4116
+ this.isRendered = true;
4079
4117
  }
4080
4118
  /**
4081
4119
  * Applies a targeted DOM update for a single configuration property change.
4082
4120
  * Safely updates classes, attributes, styles, and child elements without re-rendering the whole view.
4083
4121
  */
4084
- _applyPartialChange(prop, newValue, oldValue) {
4122
+ applyPartialChange(prop, newValue, oldValue) {
4085
4123
  const v = this.view;
4086
4124
  if (!v || !v.view)
4087
4125
  return;
@@ -4101,8 +4139,8 @@
4101
4139
  const val = !!newValue;
4102
4140
  root.classList.toggle("has-image", val);
4103
4141
  if (val) {
4104
- root.classList.add(`image-${this._config.imagePosition}`);
4105
- this._createImage();
4142
+ root.classList.add(`image-${this.config.imagePosition}`);
4143
+ this.createImage();
4106
4144
  }
4107
4145
  else {
4108
4146
  root.className = root.className.replace(/image-(top|right|bottom|left)/g, "").trim();
@@ -4115,7 +4153,7 @@
4115
4153
  break;
4116
4154
  }
4117
4155
  case "imagePosition": {
4118
- if (this._config.hasImage) {
4156
+ if (this.config.hasImage) {
4119
4157
  root.className = root.className.replace(/image-(top|right|bottom|left)/g, "").trim();
4120
4158
  root.classList.add(`image-${String(newValue)}`);
4121
4159
  }
@@ -4138,7 +4176,7 @@
4138
4176
  case "labelValign":
4139
4177
  case "labelHalign": {
4140
4178
  if (label) {
4141
- const newClass = `align-vertical-${this._config.labelValign} align-horizontal-${this._config.labelHalign}`;
4179
+ const newClass = `align-vertical-${this.config.labelValign} align-horizontal-${this.config.labelHalign}`;
4142
4180
  if (label.className !== newClass)
4143
4181
  label.className = newClass;
4144
4182
  }
@@ -4152,7 +4190,7 @@
4152
4190
  * The image receives configured styles (width, height, borderRadius) and is placed
4153
4191
  * before the label if present; otherwise appended to the root. Updates `v.tags.OptionImage`.
4154
4192
  */
4155
- _createImage() {
4193
+ createImage() {
4156
4194
  const v = this.view;
4157
4195
  if (!v || !v.view)
4158
4196
  return;
@@ -4163,9 +4201,9 @@
4163
4201
  const label = v.tags?.OptionLabel;
4164
4202
  const image = document.createElement("img");
4165
4203
  image.className = "option-image";
4166
- image.style.width = this._config.imageWidth;
4167
- image.style.height = this._config.imageHeight;
4168
- image.style.borderRadius = this._config.imageBorderRadius;
4204
+ image.style.width = this.config.imageWidth;
4205
+ image.style.height = this.config.imageHeight;
4206
+ image.style.borderRadius = this.config.imageBorderRadius;
4169
4207
  if (label && label.parentElement)
4170
4208
  root.insertBefore(image, label);
4171
4209
  else
@@ -4181,16 +4219,16 @@
4181
4219
  constructor(items = []) {
4182
4220
  super(items);
4183
4221
  this.isMultiple = false;
4184
- this._visibilityChangedCallbacks = [];
4185
- this._currentHighlightIndex = -1;
4186
- this._selectedItemSingle = null;
4222
+ this.visibilityChangedCallbacks = [];
4223
+ this.currentHighlightIndex = -1;
4224
+ this.selectedItemSingle = null;
4187
4225
  this.groups = [];
4188
4226
  this.flatOptions = [];
4189
- this._buildFlatStructure();
4227
+ this.buildFlatStructure();
4190
4228
  Libs.callbackScheduler.on(`sche_vis_${this.adapterKey}`, () => {
4191
4229
  const visibleCount = this.flatOptions.filter((item) => item.visible).length;
4192
4230
  const totalCount = this.flatOptions.length;
4193
- this._visibilityChangedCallbacks.forEach((callback) => {
4231
+ this.visibilityChangedCallbacks.forEach((callback) => {
4194
4232
  callback({
4195
4233
  visibleCount,
4196
4234
  totalCount,
@@ -4204,7 +4242,7 @@
4204
4242
  /**
4205
4243
  * Build flat list of all options for navigation
4206
4244
  */
4207
- _buildFlatStructure() {
4245
+ buildFlatStructure() {
4208
4246
  this.flatOptions = [];
4209
4247
  this.groups = [];
4210
4248
  this.items.forEach((item) => {
@@ -4240,10 +4278,10 @@
4240
4278
  onViewHolder(item, viewer, position) {
4241
4279
  item.position = position;
4242
4280
  if (item instanceof GroupModel) {
4243
- this._handleGroupView(item, viewer, position);
4281
+ this.handleGroupView(item, viewer, position);
4244
4282
  }
4245
4283
  else if (item instanceof OptionModel) {
4246
- this._handleOptionView(item, viewer, position);
4284
+ this.handleOptionView(item, viewer, position);
4247
4285
  }
4248
4286
  item.isInit = true;
4249
4287
  }
@@ -4255,7 +4293,7 @@
4255
4293
  * @param {GroupView} groupView - The view instance that renders the group in the UI.
4256
4294
  * @param {number} position - The position (index) of the group within a list.
4257
4295
  */
4258
- _handleGroupView(groupModel, groupView, position) {
4296
+ handleGroupView(groupModel, groupView, position) {
4259
4297
  super.onViewHolder(groupModel, groupView, position);
4260
4298
  groupModel.view = groupView;
4261
4299
  const header = groupView.view.tags.GroupHeader;
@@ -4280,7 +4318,7 @@
4280
4318
  if (!optionModel.isInit || !optionViewer) {
4281
4319
  optionViewer = new OptionView(itemsContainer);
4282
4320
  }
4283
- this._handleOptionView(optionModel, optionViewer, idx);
4321
+ this.handleOptionView(optionModel, optionViewer, idx);
4284
4322
  optionModel.isInit = true;
4285
4323
  });
4286
4324
  groupView.setCollapsed(groupModel.collapsed);
@@ -4294,7 +4332,7 @@
4294
4332
  * @param {OptionView} optionViewer - The view instance that renders the option in the UI.
4295
4333
  * @param {number} position - The index of this option within its group's item list.
4296
4334
  */
4297
- _handleOptionView(optionModel, optionViewer, position) {
4335
+ handleOptionView(optionModel, optionViewer, position) {
4298
4336
  optionViewer.isMultiple = this.isMultiple;
4299
4337
  optionViewer.hasImage = optionModel.hasImage;
4300
4338
  optionViewer.optionConfig = {
@@ -4320,24 +4358,20 @@
4320
4358
  }
4321
4359
  optionViewer.view.tags.LabelContent.innerHTML = optionModel.text;
4322
4360
  if (!optionModel.isInit) {
4323
- optionViewer.view.tags.OptionView.addEventListener("click", (ev) => {
4361
+ optionViewer.view.tags.OptionView.addEventListener("click", async (ev) => {
4324
4362
  ev.stopPropagation();
4325
4363
  ev.preventDefault();
4326
4364
  if (this.isSkipEvent)
4327
4365
  return;
4328
4366
  if (this.isMultiple) {
4329
- this.changingProp("select");
4330
- setTimeout(() => {
4331
- optionModel.selected = !optionModel.selected;
4332
- }, 5);
4367
+ await this.changingProp("select");
4368
+ optionModel.selected = !optionModel.selected;
4333
4369
  }
4334
4370
  else if (optionModel.selected !== true) {
4335
- this.changingProp("select");
4336
- setTimeout(() => {
4337
- if (this._selectedItemSingle)
4338
- this._selectedItemSingle.selected = false;
4339
- optionModel.selected = true;
4340
- }, 5);
4371
+ await this.changingProp("select");
4372
+ if (this.selectedItemSingle)
4373
+ this.selectedItemSingle.selected = false;
4374
+ optionModel.selected = true;
4341
4375
  }
4342
4376
  });
4343
4377
  optionViewer.view.tags.OptionView.title = optionModel.textContent;
@@ -4351,16 +4385,16 @@
4351
4385
  });
4352
4386
  optionModel.onInternalSelected((_evtToken, _el, selected) => {
4353
4387
  if (selected)
4354
- this._selectedItemSingle = optionModel;
4388
+ this.selectedItemSingle = optionModel;
4355
4389
  this.changeProp("selected_internal");
4356
4390
  });
4357
4391
  optionModel.onVisibilityChanged((_evtToken, model, _visible) => {
4358
4392
  model.group?.updateVisibility();
4359
- this._notifyVisibilityChanged();
4393
+ this.notifyVisibilityChanged();
4360
4394
  });
4361
4395
  }
4362
4396
  if (optionModel.selected) {
4363
- this._selectedItemSingle = optionModel;
4397
+ this.selectedItemSingle = optionModel;
4364
4398
  optionModel.selectedNonTrigger = true;
4365
4399
  }
4366
4400
  }
@@ -4369,19 +4403,19 @@
4369
4403
  *
4370
4404
  * @param {Array<GroupModel|OptionModel>} items - The new collection of items to be displayed.
4371
4405
  */
4372
- setItems(items) {
4373
- this.changingProp("items", items);
4406
+ async setItems(items) {
4407
+ await this.changingProp("items", items);
4374
4408
  this.items = items;
4375
- this._buildFlatStructure();
4376
- this.changeProp("items", items);
4409
+ this.buildFlatStructure();
4410
+ await this.changeProp("items", items);
4377
4411
  }
4378
4412
  /**
4379
4413
  * Synchronizes the component's items from an external source by delegating to setItems().
4380
4414
  *
4381
4415
  * @param {Array<GroupModel|OptionModel>} items - The new collection of items to sync.
4382
4416
  */
4383
- syncFromSource(items) {
4384
- this.setItems(items);
4417
+ async syncFromSource(items) {
4418
+ await this.setItems(items);
4385
4419
  }
4386
4420
  /**
4387
4421
  * Updates the component's data items and rebuilds the internal flat structure
@@ -4391,7 +4425,7 @@
4391
4425
  */
4392
4426
  updateData(items) {
4393
4427
  this.items = items;
4394
- this._buildFlatStructure();
4428
+ this.buildFlatStructure();
4395
4429
  }
4396
4430
  /**
4397
4431
  * Returns all option items that are currently selected.
@@ -4428,13 +4462,13 @@
4428
4462
  * - Function to invoke when visibility stats change.
4429
4463
  */
4430
4464
  onVisibilityChanged(callback) {
4431
- this._visibilityChangedCallbacks.push(callback);
4465
+ this.visibilityChangedCallbacks.push(callback);
4432
4466
  }
4433
4467
  /**
4434
4468
  * Notifies all registered visibility-change callbacks with up-to-date statistics.
4435
4469
  * Computes visible and total counts, then emits aggregated state.
4436
4470
  */
4437
- _notifyVisibilityChanged() {
4471
+ notifyVisibilityChanged() {
4438
4472
  Libs.callbackScheduler.run(`sche_vis_${this.adapterKey}`);
4439
4473
  }
4440
4474
  /**
@@ -4466,7 +4500,7 @@
4466
4500
  const visibleOptions = this.flatOptions.filter((opt) => opt.visible);
4467
4501
  if (visibleOptions.length === 0)
4468
4502
  return;
4469
- let currentVisibleIndex = visibleOptions.findIndex((opt) => opt === this.flatOptions[this._currentHighlightIndex]);
4503
+ let currentVisibleIndex = visibleOptions.findIndex((opt) => opt === this.flatOptions[this.currentHighlightIndex]);
4470
4504
  if (currentVisibleIndex === -1)
4471
4505
  currentVisibleIndex = -1;
4472
4506
  let nextVisibleIndex = currentVisibleIndex + direction;
@@ -4483,8 +4517,8 @@
4483
4517
  * No-op if nothing is highlighted or the highlighted item is not visible.
4484
4518
  */
4485
4519
  selectHighlighted() {
4486
- if (this._currentHighlightIndex > -1 && this.flatOptions[this._currentHighlightIndex]) {
4487
- const item = this.flatOptions[this._currentHighlightIndex];
4520
+ if (this.currentHighlightIndex > -1 && this.flatOptions[this.currentHighlightIndex]) {
4521
+ const item = this.flatOptions[this.currentHighlightIndex];
4488
4522
  if (item.visible) {
4489
4523
  const viewEl = item.view?.getView?.();
4490
4524
  if (viewEl)
@@ -4511,15 +4545,15 @@
4511
4545
  else {
4512
4546
  index = 0;
4513
4547
  }
4514
- if (this._currentHighlightIndex > -1 && this.flatOptions[this._currentHighlightIndex]) {
4515
- this.flatOptions[this._currentHighlightIndex].highlighted = false;
4548
+ if (this.currentHighlightIndex > -1 && this.flatOptions[this.currentHighlightIndex]) {
4549
+ this.flatOptions[this.currentHighlightIndex].highlighted = false;
4516
4550
  }
4517
4551
  for (let i = index; i < this.flatOptions.length; i++) {
4518
4552
  const item = this.flatOptions[i];
4519
4553
  if (!item?.visible)
4520
4554
  continue;
4521
4555
  item.highlighted = true;
4522
- this._currentHighlightIndex = i;
4556
+ this.currentHighlightIndex = i;
4523
4557
  if (isScrollToView) {
4524
4558
  const el = item.view?.getView?.();
4525
4559
  if (el) {
@@ -4628,15 +4662,15 @@
4628
4662
  this.firstMeasured = false;
4629
4663
  this.start = 0;
4630
4664
  this.end = -1;
4631
- this._rafId = null;
4632
- this._measureRaf = null;
4633
- this._updating = false;
4634
- this._suppressResize = false;
4635
- this._lastRenderCount = 0;
4636
- this._suspended = false;
4637
- this._resumeResizeAfter = false;
4638
- this._stickyCacheTick = 0;
4639
- this._stickyCacheVal = 0;
4665
+ this.rafId = null;
4666
+ this.measureRaf = null;
4667
+ this.updating = false;
4668
+ this.suppressResize = false;
4669
+ this.lastRenderCount = 0;
4670
+ this.suspended = false;
4671
+ this.resumeResizeAfter = false;
4672
+ this.stickyCacheTick = 0;
4673
+ this.stickyCacheVal = 0;
4640
4674
  this.measuredSum = 0;
4641
4675
  this.measuredCount = 0;
4642
4676
  }
@@ -4669,8 +4703,8 @@
4669
4703
  ?? this.viewElement.parentElement;
4670
4704
  if (!this.scrollEl)
4671
4705
  throw new Error("VirtualRecyclerView: scrollEl not found");
4672
- this._boundOnScroll = this.onScroll.bind(this);
4673
- this.scrollEl.addEventListener("scroll", this._boundOnScroll, { passive: true });
4706
+ this.boundOnScroll = this.onScroll.bind(this);
4707
+ this.scrollEl.addEventListener("scroll", this.boundOnScroll, { passive: true });
4674
4708
  this.refresh(false);
4675
4709
  this.attachResizeObserverOnce();
4676
4710
  adapter?.onVisibilityChanged?.(() => this.refreshItem());
@@ -4680,14 +4714,14 @@
4680
4714
  * Cancels pending frames and disconnects observers.
4681
4715
  */
4682
4716
  suspend() {
4683
- this._suspended = true;
4717
+ this.suspended = true;
4684
4718
  this.cancelFrames();
4685
- if (this.scrollEl && this._boundOnScroll) {
4686
- this.scrollEl.removeEventListener("scroll", this._boundOnScroll);
4719
+ if (this.scrollEl && this.boundOnScroll) {
4720
+ this.scrollEl.removeEventListener("scroll", this.boundOnScroll);
4687
4721
  }
4688
4722
  if (this.resizeObs) {
4689
4723
  this.resizeObs.disconnect();
4690
- this._resumeResizeAfter = true;
4724
+ this.resumeResizeAfter = true;
4691
4725
  }
4692
4726
  }
4693
4727
  /**
@@ -4695,13 +4729,13 @@
4695
4729
  * Re-attaches listeners and schedules window update.
4696
4730
  */
4697
4731
  resume() {
4698
- this._suspended = false;
4699
- if (this.scrollEl && this._boundOnScroll) {
4700
- this.scrollEl.addEventListener("scroll", this._boundOnScroll, { passive: true });
4732
+ this.suspended = false;
4733
+ if (this.scrollEl && this.boundOnScroll) {
4734
+ this.scrollEl.addEventListener("scroll", this.boundOnScroll, { passive: true });
4701
4735
  }
4702
- if (this._resumeResizeAfter) {
4736
+ if (this.resumeResizeAfter) {
4703
4737
  this.attachResizeObserverOnce();
4704
- this._resumeResizeAfter = false;
4738
+ this.resumeResizeAfter = false;
4705
4739
  }
4706
4740
  this.scheduleUpdateWindow();
4707
4741
  }
@@ -4717,7 +4751,7 @@
4717
4751
  if (!isUpdate)
4718
4752
  this.refreshItem();
4719
4753
  const count = this.adapter.itemCount();
4720
- this._lastRenderCount = count;
4754
+ this.lastRenderCount = count;
4721
4755
  if (count === 0) {
4722
4756
  this.resetState();
4723
4757
  return;
@@ -4759,8 +4793,8 @@
4759
4793
  */
4760
4794
  dispose() {
4761
4795
  this.cancelFrames();
4762
- if (this.scrollEl && this._boundOnScroll) {
4763
- this.scrollEl.removeEventListener("scroll", this._boundOnScroll);
4796
+ if (this.scrollEl && this.boundOnScroll) {
4797
+ this.scrollEl.removeEventListener("scroll", this.boundOnScroll);
4764
4798
  }
4765
4799
  this.resizeObs?.disconnect();
4766
4800
  this.created.forEach(el => el.remove());
@@ -4788,13 +4822,13 @@
4788
4822
  }
4789
4823
  /** Cancels all pending animation frames. */
4790
4824
  cancelFrames() {
4791
- if (this._rafId != null) {
4792
- cancelAnimationFrame(this._rafId);
4793
- this._rafId = null;
4825
+ if (this.rafId != null) {
4826
+ cancelAnimationFrame(this.rafId);
4827
+ this.rafId = null;
4794
4828
  }
4795
- if (this._measureRaf != null) {
4796
- cancelAnimationFrame(this._measureRaf);
4797
- this._measureRaf = null;
4829
+ if (this.measureRaf != null) {
4830
+ cancelAnimationFrame(this.measureRaf);
4831
+ this.measureRaf = null;
4798
4832
  }
4799
4833
  }
4800
4834
  /** Resets all internal state: DOM, caches, measurements. */
@@ -4872,7 +4906,7 @@
4872
4906
  containerTopInScroll() {
4873
4907
  const a = this.viewElement.getBoundingClientRect();
4874
4908
  const b = this.scrollEl.getBoundingClientRect();
4875
- return a.top - b.top + this.scrollEl.scrollTop;
4909
+ return Math.max(0, a.top - b.top + this.scrollEl.scrollTop);
4876
4910
  }
4877
4911
  /**
4878
4912
  * Returns sticky header height with 16ms cache to avoid DOM thrashing.
@@ -4880,19 +4914,19 @@
4880
4914
  */
4881
4915
  stickyTopHeight() {
4882
4916
  const now = performance.now();
4883
- if (now - this._stickyCacheTick < 16)
4884
- return this._stickyCacheVal;
4917
+ if (now - this.stickyCacheTick < 16)
4918
+ return this.stickyCacheVal;
4885
4919
  const sticky = this.scrollEl.querySelector(".selective-ui-option-handle:not(.hide)");
4886
- this._stickyCacheVal = sticky?.offsetHeight ?? 0;
4887
- this._stickyCacheTick = now;
4888
- return this._stickyCacheVal;
4920
+ this.stickyCacheVal = sticky?.offsetHeight ?? 0;
4921
+ this.stickyCacheTick = now;
4922
+ return this.stickyCacheVal;
4889
4923
  }
4890
4924
  /** Schedules window update on next frame if not already scheduled. */
4891
4925
  scheduleUpdateWindow() {
4892
- if (this._rafId != null || this._suspended)
4926
+ if (this.rafId != null || this.suspended)
4893
4927
  return;
4894
- this._rafId = requestAnimationFrame(() => {
4895
- this._rafId = null;
4928
+ this.rafId = requestAnimationFrame(() => {
4929
+ this.rafId = null;
4896
4930
  this.updateWindowInternal();
4897
4931
  });
4898
4932
  }
@@ -5014,10 +5048,10 @@
5014
5048
  if (this.resizeObs)
5015
5049
  return;
5016
5050
  this.resizeObs = new ResizeObserver(() => {
5017
- if (this._suppressResize || this._suspended || !this.adapter || this._measureRaf != null)
5051
+ if (this.suppressResize || this.suspended || !this.adapter || this.measureRaf != null)
5018
5052
  return;
5019
- this._measureRaf = requestAnimationFrame(() => {
5020
- this._measureRaf = null;
5053
+ this.measureRaf = requestAnimationFrame(() => {
5054
+ this.measureRaf = null;
5021
5055
  this.measureVisibleAndUpdate();
5022
5056
  });
5023
5057
  });
@@ -5067,17 +5101,17 @@
5067
5101
  * 7. Adjusts scroll position to maintain anchor item position
5068
5102
  */
5069
5103
  updateWindowInternal() {
5070
- if (this._updating || this._suspended)
5104
+ if (this.updating || this.suspended)
5071
5105
  return;
5072
- this._updating = true;
5106
+ this.updating = true;
5073
5107
  try {
5074
5108
  if (!this.adapter)
5075
5109
  return;
5076
5110
  const count = this.adapter.itemCount();
5077
5111
  if (count <= 0)
5078
5112
  return;
5079
- if (this._lastRenderCount !== count) {
5080
- this._lastRenderCount = count;
5113
+ if (this.lastRenderCount !== count) {
5114
+ this.lastRenderCount = count;
5081
5115
  this.heightCache.length = count;
5082
5116
  this.rebuildFenwick(count);
5083
5117
  }
@@ -5101,7 +5135,7 @@
5101
5135
  return;
5102
5136
  this.start = startIndex;
5103
5137
  this.end = endIndex;
5104
- this._suppressResize = true;
5138
+ this.suppressResize = true;
5105
5139
  try {
5106
5140
  this.mountRange(this.start, this.end);
5107
5141
  this.unmountOutside(this.start, this.end);
@@ -5115,18 +5149,20 @@
5115
5149
  this.PadBottom.style.height = `${bottomPx}px`;
5116
5150
  }
5117
5151
  finally {
5118
- this._suppressResize = false;
5152
+ this.suppressResize = false;
5119
5153
  }
5120
5154
  const anchorTopNew = this.offsetTopOf(anchorIndex);
5121
5155
  const targetScroll = this.containerTopInScroll() + anchorTopNew - anchorDelta;
5122
5156
  const maxScroll = Math.max(0, this.scrollEl.scrollHeight - this.scrollEl.clientHeight);
5123
5157
  const clamped = Math.min(Math.max(0, targetScroll), maxScroll);
5124
- if (Math.abs(this.scrollEl.scrollTop - clamped) > 0.5) {
5158
+ const heightChanged = Math.abs(anchorTopNew - anchorTop) > 1;
5159
+ const scrollDiff = Math.abs(this.scrollEl.scrollTop - clamped);
5160
+ if (heightChanged && scrollDiff > 0.5 && scrollDiff < 100) {
5125
5161
  this.scrollEl.scrollTop = clamped;
5126
5162
  }
5127
5163
  }
5128
5164
  finally {
5129
- this._updating = false;
5165
+ this.updating = false;
5130
5166
  }
5131
5167
  }
5132
5168
  /** Mounts all items in inclusive range [start..end]. */
@@ -5395,12 +5431,6 @@
5395
5431
  this.isVisible = Libs.string2Boolean(dataset.visible ?? "1");
5396
5432
  }
5397
5433
  };
5398
- // Custom event (manual refresh)
5399
- select.addEventListener("options:changed", () => {
5400
- optionModelManager.update(Libs.parseSelectToArray(select));
5401
- this.getAction()?.refreshMask();
5402
- container.popup?.triggerResize?.();
5403
- });
5404
5434
  // AJAX setup (if provided)
5405
5435
  if (options.ajax) {
5406
5436
  searchController.setAjax(options.ajax);
@@ -5418,6 +5448,7 @@
5418
5448
  .search(keyword)
5419
5449
  .then((result) => {
5420
5450
  clearTimeout(hightlightTimer);
5451
+ Libs.callbackScheduler.clear(`sche_vis_proxy_${optionAdapter.adapterKey}`);
5421
5452
  Libs.callbackScheduler.on(`sche_vis_proxy_${optionAdapter.adapterKey}`, () => {
5422
5453
  container.popup?.triggerResize?.();
5423
5454
  if (result?.hasResults) {
@@ -5875,9 +5906,9 @@
5875
5906
  */
5876
5907
  class ElementAdditionObserver {
5877
5908
  constructor() {
5878
- this._isActive = false;
5879
- this._observer = null;
5880
- this._actions = [];
5909
+ this.isActive = false;
5910
+ this.observer = null;
5911
+ this.actions = [];
5881
5912
  }
5882
5913
  /**
5883
5914
  * Registers a callback to be invoked whenever a matching element is detected being added to the DOM.
@@ -5885,13 +5916,13 @@
5885
5916
  * @param {(el: T) => void} action - Function executed with the newly added element.
5886
5917
  */
5887
5918
  onDetect(action) {
5888
- this._actions.push(action);
5919
+ this.actions.push(action);
5889
5920
  }
5890
5921
  /**
5891
5922
  * Clears all previously registered detection callbacks.
5892
5923
  */
5893
5924
  clearDetect() {
5894
- this._actions = [];
5925
+ this.actions = [];
5895
5926
  }
5896
5927
  /**
5897
5928
  * Starts observing the document for additions of elements matching the given tag.
@@ -5900,26 +5931,26 @@
5900
5931
  * @param {string} tag - The tag name to watch for (e.g., "select", "div").
5901
5932
  */
5902
5933
  start(tag) {
5903
- if (this._isActive)
5934
+ if (this.isActive)
5904
5935
  return;
5905
- this._isActive = true;
5936
+ this.isActive = true;
5906
5937
  const upperTag = tag.toUpperCase();
5907
5938
  const lowerTag = tag.toLowerCase();
5908
- this._observer = new MutationObserver((mutations) => {
5939
+ this.observer = new MutationObserver((mutations) => {
5909
5940
  for (const mutation of mutations) {
5910
5941
  mutation.addedNodes.forEach((node) => {
5911
5942
  if (node.nodeType !== 1)
5912
5943
  return;
5913
5944
  const subnode = node;
5914
5945
  if (subnode.tagName === upperTag) {
5915
- this._handle(subnode);
5946
+ this.handle(subnode);
5916
5947
  }
5917
5948
  const matches = subnode.querySelectorAll(lowerTag);
5918
- matches.forEach((el) => this._handle(el));
5949
+ matches.forEach((el) => this.handle(el));
5919
5950
  });
5920
5951
  }
5921
5952
  });
5922
- this._observer.observe(document.body, {
5953
+ this.observer.observe(document.body, {
5923
5954
  childList: true,
5924
5955
  subtree: true,
5925
5956
  });
@@ -5929,19 +5960,19 @@
5929
5960
  * No-ops if the observer is not active.
5930
5961
  */
5931
5962
  stop() {
5932
- if (!this._isActive)
5963
+ if (!this.isActive)
5933
5964
  return;
5934
- this._isActive = false;
5935
- this._observer?.disconnect();
5936
- this._observer = null;
5965
+ this.isActive = false;
5966
+ this.observer?.disconnect();
5967
+ this.observer = null;
5937
5968
  }
5938
5969
  /**
5939
5970
  * Internal handler that invokes all registered detection callbacks for the provided element.
5940
5971
  *
5941
5972
  * @param {T} element - The element that was detected as added to the DOM.
5942
5973
  */
5943
- _handle(element) {
5944
- this._actions.forEach((action) => action(element));
5974
+ handle(element) {
5975
+ this.actions.forEach((action) => action(element));
5945
5976
  }
5946
5977
  }
5947
5978
 
@@ -6256,7 +6287,7 @@
6256
6287
  if (typeof globalThis.GLOBAL_SEUI == "undefined") {
6257
6288
  const SECLASS = new Selective();
6258
6289
  globalThis.GLOBAL_SEUI = {
6259
- version: "1.2.1",
6290
+ version: "1.2.3",
6260
6291
  name: "SelectiveUI",
6261
6292
  bind: SECLASS.bind.bind(SECLASS),
6262
6293
  find: SECLASS.find.bind(SECLASS),
@@ -6287,7 +6318,7 @@
6287
6318
  init();
6288
6319
  }
6289
6320
  }
6290
- console.log(`[${"SelectiveUI"}] v${"1.2.1"} loaded successfully`);
6321
+ console.log(`[${"SelectiveUI"}] v${"1.2.3"} loaded successfully`);
6291
6322
  }
6292
6323
  else {
6293
6324
  console.warn(`[${globalThis.GLOBAL_SEUI.name}] Already loaded (v${globalThis.GLOBAL_SEUI.version}). ` +