selective-ui 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/selective-ui.css +2 -2
  2. package/dist/selective-ui.css.map +1 -1
  3. package/dist/selective-ui.esm.js +407 -573
  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 +409 -575
  12. package/dist/selective-ui.umd.js.map +1 -1
  13. package/package.json +12 -12
  14. package/src/css/views/option-view.css +2 -2
  15. package/src/ts/adapter/mixed-adapter.ts +149 -71
  16. package/src/ts/components/accessorybox.ts +14 -11
  17. package/src/ts/components/directive.ts +1 -1
  18. package/src/ts/components/option-handle.ts +12 -9
  19. package/src/ts/components/placeholder.ts +5 -5
  20. package/src/ts/components/popup/empty-state.ts +5 -5
  21. package/src/ts/components/popup/loading-state.ts +5 -5
  22. package/src/ts/components/popup/popup.ts +138 -76
  23. package/src/ts/components/searchbox.ts +17 -13
  24. package/src/ts/components/selectbox.ts +260 -84
  25. package/src/ts/core/base/adapter.ts +61 -14
  26. package/src/ts/core/base/fenwick.ts +3 -2
  27. package/src/ts/core/base/lifecycle.ts +14 -4
  28. package/src/ts/core/base/model.ts +17 -15
  29. package/src/ts/core/base/recyclerview.ts +7 -5
  30. package/src/ts/core/base/view.ts +10 -5
  31. package/src/ts/core/base/virtual-recyclerview.ts +178 -45
  32. package/src/ts/core/model-manager.ts +48 -21
  33. package/src/ts/core/search-controller.ts +174 -56
  34. package/src/ts/global.ts +5 -8
  35. package/src/ts/index.ts +2 -2
  36. package/src/ts/models/group-model.ts +33 -8
  37. package/src/ts/models/option-model.ts +88 -20
  38. package/src/ts/services/dataset-observer.ts +6 -3
  39. package/src/ts/services/ea-observer.ts +1 -1
  40. package/src/ts/services/effector.ts +22 -12
  41. package/src/ts/services/refresher.ts +14 -4
  42. package/src/ts/services/resize-observer.ts +24 -11
  43. package/src/ts/services/select-observer.ts +2 -2
  44. package/src/ts/types/components/popup.type.ts +18 -1
  45. package/src/ts/types/components/searchbox.type.ts +43 -30
  46. package/src/ts/types/components/state.box.type.ts +1 -1
  47. package/src/ts/types/core/base/adapter.type.ts +13 -5
  48. package/src/ts/types/core/base/lifecycle.type.ts +1 -2
  49. package/src/ts/types/core/base/model.type.ts +3 -3
  50. package/src/ts/types/core/base/recyclerview.type.ts +7 -5
  51. package/src/ts/types/core/base/view.type.ts +6 -6
  52. package/src/ts/types/core/base/virtual-recyclerview.type.ts +45 -46
  53. package/src/ts/types/core/search-controller.type.ts +18 -2
  54. package/src/ts/types/css.d.ts +1 -0
  55. package/src/ts/types/plugins/plugin.type.ts +2 -2
  56. package/src/ts/types/services/effector.type.ts +25 -25
  57. package/src/ts/types/services/resize-observer.type.ts +23 -12
  58. package/src/ts/types/utils/callback-scheduler.type.ts +2 -2
  59. package/src/ts/types/utils/ievents.type.ts +1 -1
  60. package/src/ts/types/utils/istorage.type.ts +62 -60
  61. package/src/ts/types/utils/libs.type.ts +19 -17
  62. package/src/ts/types/utils/selective.type.ts +6 -3
  63. package/src/ts/types/views/view.group.type.ts +9 -5
  64. package/src/ts/types/views/view.option.type.ts +39 -17
  65. package/src/ts/utils/callback-scheduler.ts +12 -7
  66. package/src/ts/utils/ievents.ts +12 -5
  67. package/src/ts/utils/istorage.ts +5 -3
  68. package/src/ts/utils/libs.ts +122 -43
  69. package/src/ts/utils/selective.ts +15 -8
  70. package/src/ts/views/group-view.ts +11 -9
  71. package/src/ts/views/option-view.ts +37 -18
@@ -1,4 +1,4 @@
1
- /*! Selective UI v1.4.0 | MIT License */
1
+ /*! Selective UI v1.4.2 | MIT License */
2
2
  /**
3
3
  * @class
4
4
  */
@@ -147,7 +147,7 @@ class CallbackScheduler {
147
147
  *
148
148
  * @public
149
149
  * @param {TimerKey} key - Group identifier for callbacks.
150
- * @param {(payload: any[] | null) => void} callback - Function to execute after debounce timeout.
150
+ * @param {(payload?: any[]) => void} callback - Function to execute after debounce timeout.
151
151
  * @param {TimerOptions} [options={}] - Scheduling options (`debounce`, `once`).
152
152
  * @returns {void}
153
153
  */
@@ -235,7 +235,8 @@ class CallbackScheduler {
235
235
  await resp;
236
236
  }
237
237
  }
238
- catch { }
238
+ catch {
239
+ }
239
240
  finally {
240
241
  if (entry.once) {
241
242
  executes[i] = undefined;
@@ -368,9 +369,11 @@ class Libs {
368
369
  * @param {boolean} systemNodeCreate - If true, do not clone; use original node.
369
370
  * @returns {HTMLElement} - The processed element.
370
371
  */
371
- static nodeCloner(node = document.documentElement, _nodeOption = null, systemNodeCreate = false) {
372
+ static nodeCloner(node = document.documentElement, _nodeOption, systemNodeCreate = false) {
372
373
  const nodeOption = { ...(_nodeOption ?? {}) };
373
- const element_creation = systemNodeCreate ? node : node.cloneNode(true);
374
+ const element_creation = systemNodeCreate
375
+ ? node
376
+ : node.cloneNode(true);
374
377
  const classList = nodeOption.classList;
375
378
  if (typeof classList === "string") {
376
379
  element_creation.classList.add(classList);
@@ -442,11 +445,13 @@ class Libs {
442
445
  * @param {TTags|Object} [recursiveTemp={}] - Accumulator for tag references.
443
446
  * @returns {TTags} - Tag map or the final mount result.
444
447
  */
445
- static mountNode(rawObj, parentE = null, isPrepend = false, isRecusive = false, recursiveTemp = {}) {
448
+ static mountNode(rawObj, parentE, isPrepend = false, isRecusive = false, recursiveTemp = {}) {
446
449
  let view = null;
447
450
  for (const key in rawObj) {
448
451
  const singleObj = rawObj[key];
449
- const tag = singleObj?.tag?.tagName ? singleObj.tag : this.nodeCreator(singleObj.tag);
452
+ const tag = singleObj?.tag?.tagName
453
+ ? singleObj.tag
454
+ : this.nodeCreator(singleObj.tag);
450
455
  recursiveTemp[key] = tag;
451
456
  if (singleObj?.child)
452
457
  this.mountNode(singleObj.child, tag, false, false, recursiveTemp);
@@ -663,7 +668,8 @@ class Libs {
663
668
  n.removeAttribute(name);
664
669
  return;
665
670
  }
666
- if (/^(href|src|xlink:href)$/i.test(name) && /^javascript:/i.test(value)) {
671
+ if (/^(href|src|xlink:href)$/i.test(name) &&
672
+ /^javascript:/i.test(value)) {
667
673
  n.removeAttribute(name);
668
674
  }
669
675
  }
@@ -693,7 +699,10 @@ class Libs {
693
699
  static string2normalize(str) {
694
700
  if (str == null)
695
701
  return "";
696
- const s = String(str).toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
702
+ const s = String(str)
703
+ .toLowerCase()
704
+ .normalize("NFD")
705
+ .replace(/[\u0300-\u036f]/g, "");
697
706
  return s.replace(/đ/g, "d").replace(/Đ/g, "d");
698
707
  }
699
708
  /**
@@ -735,7 +744,8 @@ class Libs {
735
744
  */
736
745
  static IsIOS() {
737
746
  const ua = navigator.userAgent;
738
- return /iP(hone|ad|od)/.test(ua) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
747
+ return (/iP(hone|ad|od)/.test(ua) ||
748
+ (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1));
739
749
  }
740
750
  /**
741
751
  * Converts an arbitrary CSS size value into pixel units by measuring a temporary element.
@@ -757,14 +767,16 @@ class Libs {
757
767
  if (v.endsWith("rem"))
758
768
  return fs * parseFloat(v) + "px";
759
769
  // fallback: DOM measure
760
- const el = this.nodeCreator({ node: "div", style: { height: v, opacity: "0" } });
770
+ const el = this.nodeCreator({
771
+ node: "div",
772
+ style: { height: v, opacity: "0" },
773
+ });
761
774
  document.body.appendChild(el);
762
775
  const px = el.offsetHeight + "px";
763
776
  el.remove();
764
777
  return px;
765
778
  }
766
779
  }
767
- Libs._iStorage = null;
768
780
  /**
769
781
  * Schedules and batches function executions keyed by name, with debounced timers.
770
782
  * Provides setExecute(), clearExecute(), and run() to manage deferred callbacks.
@@ -960,8 +972,9 @@ class Refresher {
960
972
  *
961
973
  * @param select - Native `<select>` element used as the sizing reference and option source.
962
974
  * @param view - View panel element whose inline styles will be updated.
975
+ * @param isWidthOnly - If true, only the width will be updated; height will be left unchanged.
963
976
  */
964
- static resizeBox(select, view) {
977
+ static resizeBox(select, view, isWidthOnly = false) {
965
978
  const bindedMap = Libs.getBinderMap(select);
966
979
  if (!bindedMap?.options)
967
980
  return;
@@ -981,7 +994,12 @@ class Refresher {
981
994
  width = options.width;
982
995
  if (cfgHeight > 0)
983
996
  height = options.height;
984
- Object.assign(view.style, { width, height, minWidth, minHeight });
997
+ if (isWidthOnly) {
998
+ Object.assign(view.style, { width, maxWidth: width, minWidth });
999
+ }
1000
+ else {
1001
+ Object.assign(view.style, { width, height, maxWidth: width, minWidth, minHeight });
1002
+ }
985
1003
  }
986
1004
  }
987
1005
 
@@ -1291,25 +1309,6 @@ class PlaceHolder extends Lifecycle {
1291
1309
  */
1292
1310
  constructor(options) {
1293
1311
  super();
1294
- /**
1295
- * Root DOM element for the placeholder.
1296
- *
1297
- * Created during {@link initialize}. Removed from the DOM during {@link destroy}.
1298
- * `null` before initialization and after destruction.
1299
- */
1300
- this.node = null;
1301
- /**
1302
- * Configuration snapshot used to render and optionally persist placeholder content.
1303
- *
1304
- * Key fields used by this component:
1305
- * - `placeholder`: initial/current placeholder text/markup
1306
- * - `allowHtml`: controls whether HTML is rendered or stripped
1307
- *
1308
- * Cleared during {@link destroy}.
1309
- *
1310
- * @internal
1311
- */
1312
- this.options = null;
1313
1312
  if (options)
1314
1313
  this.initialize(options);
1315
1314
  }
@@ -1534,34 +1533,8 @@ class OptionHandle extends Lifecycle {
1534
1533
  *
1535
1534
  * @param options - Feature flags and labels for the two actions.
1536
1535
  */
1537
- constructor(options = null) {
1536
+ constructor(options) {
1538
1537
  super();
1539
- /**
1540
- * Result returned by {@link Libs.mountNode}.
1541
- *
1542
- * Stores the mounted view structure so the component can keep a stable reference
1543
- * to its created DOM nodes. `null` before {@link initialize}.
1544
- *
1545
- * @internal
1546
- */
1547
- this.nodeMounted = null;
1548
- /**
1549
- * Root element of this control.
1550
- *
1551
- * Created during {@link initialize}. This node is used by {@link show}/{@link hide}
1552
- * and removed during {@link destroy}.
1553
- */
1554
- this.node = null;
1555
- /**
1556
- * Configuration snapshot used for:
1557
- * - labels (`textSelectAll`, `textDeselectAll`)
1558
- * - feature flags (`multiple`, `selectall`)
1559
- *
1560
- * Treated as read-only after initialization; cleared on {@link destroy}.
1561
- *
1562
- * @internal
1563
- */
1564
- this.options = null;
1565
1538
  /**
1566
1539
  * Callback list invoked when the "Select all" control is activated.
1567
1540
  *
@@ -1649,7 +1622,8 @@ class OptionHandle extends Lifecycle {
1649
1622
  available() {
1650
1623
  if (!this.options)
1651
1624
  return false;
1652
- return Libs.string2Boolean(this.options.multiple) && Libs.string2Boolean(this.options.selectall);
1625
+ return (Libs.string2Boolean(this.options.multiple) &&
1626
+ Libs.string2Boolean(this.options.selectall));
1653
1627
  }
1654
1628
  /**
1655
1629
  * Re-evaluates visibility and advances the lifecycle update step.
@@ -1706,7 +1680,7 @@ class OptionHandle extends Lifecycle {
1706
1680
  *
1707
1681
  * @param action - Callback invoked on activation; ignored when not a function.
1708
1682
  */
1709
- onSelectAll(action = null) {
1683
+ onSelectAll(action) {
1710
1684
  if (typeof action === "function") {
1711
1685
  this.actionOnSelectAll.push(action);
1712
1686
  }
@@ -1722,7 +1696,7 @@ class OptionHandle extends Lifecycle {
1722
1696
  *
1723
1697
  * @param action - Callback invoked on activation; ignored when not a function.
1724
1698
  */
1725
- onDeSelectAll(action = null) {
1699
+ onDeSelectAll(action) {
1726
1700
  if (typeof action === "function") {
1727
1701
  this.actionOnDeSelectAll.push(action);
1728
1702
  }
@@ -1790,26 +1764,10 @@ class EmptyState extends Lifecycle {
1790
1764
  * If `options` are provided, initialization runs immediately (creates {@link node} and
1791
1765
  * transitions to `INITIALIZED`).
1792
1766
  *
1793
- * @param {SelectiveOptions | null} [options=null] - Configuration containing empty state messages.
1767
+ * @param {SelectiveOptions} [options=null] - Configuration containing empty state messages.
1794
1768
  */
1795
- constructor(options = null) {
1769
+ constructor(options) {
1796
1770
  super();
1797
- /**
1798
- * Root DOM element for the empty state UI.
1799
- *
1800
- * - Created during {@link initialize}.
1801
- * - Intended to be appended by the parent container (component does not auto-attach).
1802
- * - Removed from DOM during {@link destroy}.
1803
- */
1804
- this.node = null;
1805
- /**
1806
- * Configuration source for empty state messages.
1807
- *
1808
- * Expected to provide at least:
1809
- * - `textNoData` (for `"nodata"`)
1810
- * - `textNotFound` (for `"notfound"`)
1811
- */
1812
- this.options = null;
1813
1771
  if (options)
1814
1772
  this.initialize(options);
1815
1773
  }
@@ -1935,25 +1893,10 @@ class LoadingState extends Lifecycle {
1935
1893
  * If `options` are provided, initialization runs immediately (creates {@link node} and
1936
1894
  * transitions to `INITIALIZED`).
1937
1895
  *
1938
- * @param {SelectiveOptions | null} [options=null] - Configuration containing the loading message text.
1896
+ * @param {SelectiveOptions} [options=null] - Configuration containing the loading message text.
1939
1897
  */
1940
- constructor(options = null) {
1898
+ constructor(options) {
1941
1899
  super();
1942
- /**
1943
- * Root DOM element for the loading state UI.
1944
- *
1945
- * - Created during {@link initialize}.
1946
- * - Intended to be appended by the parent container (component does not auto-attach).
1947
- * - Removed from DOM during {@link destroy}.
1948
- */
1949
- this.node = null;
1950
- /**
1951
- * Configuration source for loading message text.
1952
- *
1953
- * Expected to provide:
1954
- * - `textLoading` (displayed while loading is active)
1955
- */
1956
- this.options = null;
1957
1900
  if (options)
1958
1901
  this.initialize(options);
1959
1902
  }
@@ -2111,27 +2054,6 @@ class ResizeObserverService {
2111
2054
  * It does **not** indicate that observers are currently attached (see {@link connect}).
2112
2055
  */
2113
2056
  this.isInit = false;
2114
- /**
2115
- * The currently bound DOM element being observed.
2116
- *
2117
- * @remarks
2118
- * Set by {@link connect} and cleared by {@link disconnect}.
2119
- */
2120
- this.element = null;
2121
- /**
2122
- * Underlying `ResizeObserver` instance.
2123
- *
2124
- * @remarks
2125
- * Allocated on {@link connect}. Disconnected and nulled on {@link disconnect}.
2126
- */
2127
- this.resizeObserver = null;
2128
- /**
2129
- * Underlying `MutationObserver` instance watching `style` and `class` attribute changes.
2130
- *
2131
- * @remarks
2132
- * Allocated on {@link connect}. Disconnected and nulled on {@link disconnect}.
2133
- */
2134
- this.mutationObserver = null;
2135
2057
  this.isInit = true;
2136
2058
  this.boundUpdateChanged = this.updateChanged.bind(this);
2137
2059
  }
@@ -2177,7 +2099,8 @@ class ResizeObserverService {
2177
2099
  return;
2178
2100
  }
2179
2101
  const rect = el.getBoundingClientRect();
2180
- const style = typeof window !== "undefined" && typeof window.getComputedStyle === "function"
2102
+ const style = typeof window !== "undefined" &&
2103
+ typeof window.getComputedStyle === "function"
2181
2104
  ? window.getComputedStyle(el)
2182
2105
  : null;
2183
2106
  const metrics = {
@@ -2309,36 +2232,10 @@ class Popup extends Lifecycle {
2309
2232
  * @param options - Configuration options (panel sizing, flags, texts, etc.).
2310
2233
  * @param modelManager - Model manager that supplies the adapter and recycler view.
2311
2234
  */
2312
- constructor(select = null, options = null, modelManager = null) {
2235
+ constructor(select, options, modelManager) {
2313
2236
  super();
2314
- /** Active configuration for the popup behavior and text labels */
2315
- this.options = null;
2316
2237
  /** Indicates whether the popup DOM has been attached to the document body at least once */
2317
2238
  this.isCreated = false;
2318
- /** Mixed adapter handling items/models and visibility stats */
2319
- this.optionAdapter = null;
2320
- /** Root popup container (the floating panel) */
2321
- this.node = null;
2322
- /** Effector service used to measure/animate the popup */
2323
- this.effSvc = null;
2324
- /** Resize observer to react to parent panel size changes */
2325
- this.resizeObser = null;
2326
- /** Binder map for parent elements (anchors to compute placement from) */
2327
- this.parent = null;
2328
- /** Header control exposing "Select All / Deselect All" actions */
2329
- this.optionHandle = null;
2330
- /** "Empty / Not found" feedback component */
2331
- this.emptyState = null;
2332
- /** Loading indicator component */
2333
- this.loadingState = null;
2334
- /** Virtualized recycler view for performant lists */
2335
- this.recyclerView = null;
2336
- /** Container that holds the list of options */
2337
- this.optionsContainer = null;
2338
- /** Scroll handler used by infinite scroll */
2339
- this.scrollListener = null;
2340
- /** Handle to defer hiding the loading indicator */
2341
- this.hideLoadHandle = null;
2342
2239
  /** Default virtual scroll configuration (tuned for typical option heights) */
2343
2240
  this.virtualScrollConfig = {
2344
2241
  /** Estimated item height in pixels (improves initial layout calculation) */
@@ -2346,7 +2243,7 @@ class Popup extends Lifecycle {
2346
2243
  /** Number of extra items to render above/below the viewport */
2347
2244
  overscan: 8,
2348
2245
  /** Whether the list contains items with dynamic (non-uniform) heights */
2349
- dynamicHeights: true
2246
+ dynamicHeights: true,
2350
2247
  };
2351
2248
  this.modelManager = modelManager;
2352
2249
  if (select && options) {
@@ -2393,7 +2290,8 @@ class Popup extends Lifecycle {
2393
2290
  },
2394
2291
  }, null);
2395
2292
  this.node = nodeMounted.view;
2396
- this.optionsContainer = nodeMounted.tags.OptionsContainer;
2293
+ this.optionsContainer = nodeMounted.tags
2294
+ .OptionsContainer;
2397
2295
  this.parent = Libs.getBinderMap(select);
2398
2296
  this.options = options;
2399
2297
  this.init();
@@ -2402,7 +2300,7 @@ class Popup extends Lifecycle {
2402
2300
  scrollEl: this.node,
2403
2301
  estimateItemHeight: this.virtualScrollConfig.estimateItemHeight,
2404
2302
  overscan: this.virtualScrollConfig.overscan,
2405
- dynamicHeights: this.virtualScrollConfig.dynamicHeights
2303
+ dynamicHeights: this.virtualScrollConfig.dynamicHeights,
2406
2304
  }
2407
2305
  : {};
2408
2306
  // Load ModelManager resources into the list container
@@ -2429,7 +2327,11 @@ class Popup extends Lifecycle {
2429
2327
  * - Triggers a resize to accommodate layout changes
2430
2328
  */
2431
2329
  async showLoading() {
2432
- if (!this.options || !this.loadingState || !this.optionHandle || !this.optionAdapter || !this.modelManager)
2330
+ if (!this.options ||
2331
+ !this.loadingState ||
2332
+ !this.optionHandle ||
2333
+ !this.optionAdapter ||
2334
+ !this.modelManager)
2433
2335
  return;
2434
2336
  if (this.hideLoadHandle)
2435
2337
  clearTimeout(this.hideLoadHandle);
@@ -2447,7 +2349,10 @@ class Popup extends Lifecycle {
2447
2349
  * Debounce: Uses `animationtime` as a short delay before hiding the loading indicator.
2448
2350
  */
2449
2351
  async hideLoading() {
2450
- if (!this.options || !this.loadingState || !this.optionAdapter || !this.modelManager)
2352
+ if (!this.options ||
2353
+ !this.loadingState ||
2354
+ !this.optionAdapter ||
2355
+ !this.modelManager)
2451
2356
  return;
2452
2357
  if (this.hideLoadHandle)
2453
2358
  clearTimeout(this.hideLoadHandle);
@@ -2488,7 +2393,10 @@ class Popup extends Lifecycle {
2488
2393
  * @param stats - Optionally provide precomputed visibility stats.
2489
2394
  */
2490
2395
  updateEmptyState(stats) {
2491
- if (!this.optionAdapter || !this.emptyState || !this.optionHandle || !this.optionsContainer)
2396
+ if (!this.optionAdapter ||
2397
+ !this.emptyState ||
2398
+ !this.optionHandle ||
2399
+ !this.optionsContainer)
2492
2400
  return;
2493
2401
  const s = stats ?? this.optionAdapter.getVisibilityStats();
2494
2402
  if (s.isEmpty) {
@@ -2572,8 +2480,12 @@ class Popup extends Lifecycle {
2572
2480
  * @param callback - Optional callback invoked when the opening animation completes.
2573
2481
  * @param isShowEmptyState - If true, applies the empty/not-found state before animation.
2574
2482
  */
2575
- open(callback = null, isShowEmptyState) {
2576
- if (!this.node || !this.options || !this.optionHandle || !this.parent || !this.effSvc)
2483
+ open(callback, isShowEmptyState) {
2484
+ if (!this.node ||
2485
+ !this.options ||
2486
+ !this.optionHandle ||
2487
+ !this.parent ||
2488
+ !this.effSvc)
2577
2489
  return;
2578
2490
  // Ensure one-time initialization
2579
2491
  this.load();
@@ -2622,8 +2534,11 @@ class Popup extends Lifecycle {
2622
2534
  *
2623
2535
  * @param callback - Optional callback invoked when the closing animation completes.
2624
2536
  */
2625
- close(callback = null) {
2626
- if (!this.isCreated || !this.options || !this.resizeObser || !this.effSvc)
2537
+ close(callback) {
2538
+ if (!this.isCreated ||
2539
+ !this.options ||
2540
+ !this.resizeObser ||
2541
+ !this.effSvc)
2627
2542
  return;
2628
2543
  const rv = this.recyclerView;
2629
2544
  rv?.suspend?.();
@@ -2782,7 +2697,9 @@ class Popup extends Lifecycle {
2782
2697
  let maxHeight = configMaxHeight;
2783
2698
  let realHeight = Math.min(contentHeight, maxHeight);
2784
2699
  const heightOri = spaceBelow - safeMargin;
2785
- if (realHeight >= configMinHeight ? heightOri >= configMinHeight : heightOri >= realHeight) {
2700
+ if (realHeight >= configMinHeight
2701
+ ? heightOri >= configMinHeight
2702
+ : heightOri >= realHeight) {
2786
2703
  position = "bottom";
2787
2704
  maxHeight = Math.min(spaceBelow - safeMargin, configMaxHeight);
2788
2705
  }
@@ -2882,78 +2799,8 @@ class SearchBox extends Lifecycle {
2882
2799
  *
2883
2800
  * @param options - Configuration such as placeholder, accessibility IDs, and flags.
2884
2801
  */
2885
- constructor(options = null) {
2802
+ constructor(options) {
2886
2803
  super();
2887
- /**
2888
- * The mount result returned by {@link Libs.mountNode}.
2889
- *
2890
- * Provides typed access to created DOM tags (e.g., `SearchInput`) and the root view.
2891
- * `null` before initialization and after destruction.
2892
- *
2893
- * @internal
2894
- */
2895
- this.nodeMounted = null;
2896
- /**
2897
- * Root container node of this component.
2898
- *
2899
- * Created during {@link initialize} and removed during {@link destroy}.
2900
- * Visibility is controlled by adding/removing the `hide` class.
2901
- */
2902
- this.node = null;
2903
- /**
2904
- * The `<input type="search">` element used to capture user queries.
2905
- *
2906
- * Cached for imperative operations (focus, placeholder updates, ARIA updates).
2907
- * `null` before initialization and after destruction.
2908
- *
2909
- * @internal
2910
- */
2911
- this.SearchInput = null;
2912
- /**
2913
- * External "search changed" hook.
2914
- *
2915
- * Invoked when the user edits text (via the `input` event) and the edit is not
2916
- * part of a handled control-key sequence (e.g., ArrowUp/Down/Tab/Enter/Escape).
2917
- *
2918
- * Ownership:
2919
- * - Implementations typically filter adapter/model state and refresh the list.
2920
- */
2921
- this.onSearch = null;
2922
- /**
2923
- * Options snapshot used for behavior toggles and attributes.
2924
- *
2925
- * Key fields typically consumed here:
2926
- * - `placeholder`: initial placeholder string
2927
- * - `searchable`: toggles readOnly + focus behavior on {@link show}
2928
- * - `SEID_LIST`: used as `aria-controls` value to bind to listbox container
2929
- *
2930
- * Cleared during {@link destroy}.
2931
- *
2932
- * @internal
2933
- */
2934
- this.options = null;
2935
- /**
2936
- * External navigation hook for list traversal.
2937
- *
2938
- * Called with:
2939
- * - `+1` for forward (ArrowDown / Tab)
2940
- * - `-1` for backward (ArrowUp)
2941
- *
2942
- * Typical consumers update highlight/active option in Adapter/RecyclerView.
2943
- */
2944
- this.onNavigate = null;
2945
- /**
2946
- * External "commit" hook (Enter key).
2947
- *
2948
- * Typical consumers confirm selection of the highlighted option or submit the current state.
2949
- */
2950
- this.onEnter = null;
2951
- /**
2952
- * External "cancel" hook (Escape key).
2953
- *
2954
- * Typical consumers close the popup, clear highlight, or reset interaction mode.
2955
- */
2956
- this.onEsc = null;
2957
2804
  this.options = options;
2958
2805
  if (options)
2959
2806
  this.initialize(options);
@@ -3219,17 +3066,7 @@ class EffectorImpl {
3219
3066
  *
3220
3067
  * @param query - CSS selector or element to control. When `null`, instance starts unbound.
3221
3068
  */
3222
- constructor(query = null) {
3223
- /**
3224
- * Timeout used to finalize expand/collapse/swipe animations.
3225
- * Cleared by {@link cancel}.
3226
- */
3227
- this.timeOut = null;
3228
- /**
3229
- * Timeout used to clear transitions after resize in non-animated scenarios.
3230
- * Cleared by {@link cancel}.
3231
- */
3232
- this.resizeTimeout = null;
3069
+ constructor(query) {
3233
3070
  /**
3234
3071
  * Internal animation flag set while a timed animation is in-flight.
3235
3072
  *
@@ -3413,7 +3250,9 @@ class EffectorImpl {
3413
3250
  const { duration = 200, onComplete } = config;
3414
3251
  const currentHeight = this.element.offsetHeight;
3415
3252
  const currentTop = this.element.offsetTop;
3416
- const position = this.element.classList.contains("position-top") ? "top" : "bottom";
3253
+ const position = this.element.classList.contains("position-top")
3254
+ ? "top"
3255
+ : "bottom";
3417
3256
  const isScrollable = this.element.scrollHeight - this.element.offsetHeight > 0;
3418
3257
  const finalTop = position === "top" ? currentTop + currentHeight : currentTop;
3419
3258
  requestAnimationFrame(() => {
@@ -3551,7 +3390,9 @@ class EffectorImpl {
3551
3390
  return this;
3552
3391
  this.cancel();
3553
3392
  const { duration = 200, width, left, top, maxHeight, realHeight, position = "bottom", animate = true, onComplete, } = config;
3554
- const currentPosition = this.element.classList.contains("position-top") ? "top" : "bottom";
3393
+ const currentPosition = this.element.classList.contains("position-top")
3394
+ ? "top"
3395
+ : "bottom";
3555
3396
  const isPositionChanged = currentPosition !== position;
3556
3397
  const isScrollable = this.element.scrollHeight > maxHeight;
3557
3398
  this.element.classList.toggle("position-top", position === "top");
@@ -3667,25 +3508,11 @@ class Model extends Lifecycle {
3667
3508
  * - Calls {@link Lifecycle.init} immediately (`NEW → INITIALIZED`).
3668
3509
  *
3669
3510
  * @param {TOptions} options - Configuration options for the model.
3670
- * @param {TTarget | null} [targetElement=null] - Optional DOM element to bind.
3671
- * @param {TView | null} [view=null] - Optional view responsible for rendering this model.
3511
+ * @param {TTarget} [targetElement=null] - Optional DOM element to bind.
3512
+ * @param {TView} [view=null] - Optional view responsible for rendering this model.
3672
3513
  */
3673
- constructor(options, targetElement = null, view = null) {
3514
+ constructor(options, targetElement, view) {
3674
3515
  super();
3675
- /**
3676
- * The currently bound target DOM element.
3677
- *
3678
- * This element typically represents the source-of-truth node in the host DOM (e.g., a native `<option>`).
3679
- * May be replaced via {@link updateTarget} during reconciliation.
3680
- */
3681
- this.targetElement = null;
3682
- /**
3683
- * View instance responsible for rendering this model.
3684
- *
3685
- * Ownership: this model will destroy the view on {@link destroy}.
3686
- * The view may be attached/assigned by external orchestrators (Adapter/RecyclerView) after construction.
3687
- */
3688
- this.view = null;
3689
3516
  /**
3690
3517
  * Position index used by list infrastructure for ordering/tracking.
3691
3518
  * Semantics are library-specific (e.g., top-level index or adapter position).
@@ -3717,7 +3544,7 @@ class Model extends Lifecycle {
3717
3544
  * - Assigns {@link targetElement}.
3718
3545
  * - Calls {@link Lifecycle.update} (guarded by lifecycle state).
3719
3546
  *
3720
- * @param {TTarget | null} targetElement - The new DOM element to associate with this model.
3547
+ * @param {TTarget} targetElement - The new DOM element to associate with this model.
3721
3548
  * @returns {void}
3722
3549
  */
3723
3550
  updateTarget(targetElement) {
@@ -3917,7 +3744,7 @@ class GroupModel extends Model {
3917
3744
  if (this.is(LifecycleState.DESTROYED)) {
3918
3745
  return;
3919
3746
  }
3920
- this.items.forEach(item => {
3747
+ this.items.forEach((item) => {
3921
3748
  item.destroy();
3922
3749
  });
3923
3750
  this.items = [];
@@ -4041,10 +3868,10 @@ class OptionModel extends Model {
4041
3868
  * Creates an option model.
4042
3869
  *
4043
3870
  * @param {SelectiveOptions} options - Shared configuration for models/views.
4044
- * @param {HTMLOptionElement | null} [targetElement=null] - Backing `<option>` element.
4045
- * @param {OptionView | null} [view=null] - Optional view used to render this model.
3871
+ * @param {HTMLOptionElement} [targetElement=null] - Backing `<option>` element.
3872
+ * @param {OptionView} [view=null] - Optional view used to render this model.
4046
3873
  */
4047
- constructor(options, targetElement = null, view = null) {
3874
+ constructor(options, targetElement, view) {
4048
3875
  super(options, targetElement, view);
4049
3876
  /**
4050
3877
  * External selection subscribers (emitted by the {@link selected} setter).
@@ -4068,11 +3895,6 @@ class OptionModel extends Model {
4068
3895
  this._visible = true;
4069
3896
  /** Highlight flag used for keyboard navigation / hover. */
4070
3897
  this._highlighted = false;
4071
- /**
4072
- * Parent group model (if this option belongs to a group).
4073
- * Assigned by grouping logic (e.g., GroupModel/MixedAdapter).
4074
- */
4075
- this.group = null;
4076
3898
  }
4077
3899
  /**
4078
3900
  * Initializes the model and precomputes the search key.
@@ -4201,6 +4023,32 @@ class OptionModel extends Model {
4201
4023
  }
4202
4024
  iEvents.callEvent([this, value], ...this.privOnInternalSelected);
4203
4025
  }
4026
+ /**
4027
+ * Resolved display mask for this option.
4028
+ *
4029
+ * The mask is the primary render label used by the UI layer and supports
4030
+ * optional inline tag translation / rich HTML rendering.
4031
+ *
4032
+ * Source priority:
4033
+ * 1. `data-mask` (`dataset.mask`)
4034
+ * 2. Native `<option>` text content (`targetElement.text`)
4035
+ *
4036
+ * Processing pipeline:
4037
+ * - Raw content is first passed through {@link Libs.tagTranslate}.
4038
+ * - When `options.allowHtml === true`, translated HTML is preserved.
4039
+ * - Otherwise, all markup is stripped via {@link Libs.stripHtml}.
4040
+ *
4041
+ * Unlike {@link text}, this getter prioritizes the custom dataset mask,
4042
+ * making it suitable for display overrides without mutating the native
4043
+ * `<option>` label.
4044
+ *
4045
+ * @returns {string} Render-ready option label.
4046
+ */
4047
+ get mask() {
4048
+ const raw = this.dataset?.mask ?? this.targetElement?.text ?? "";
4049
+ const translated = Libs.tagTranslate(raw);
4050
+ return this.options.allowHtml ? translated : Libs.stripHtml(translated);
4051
+ }
4204
4052
  /**
4205
4053
  * Display label for rendering (with tag translation and HTML policy).
4206
4054
  *
@@ -4214,7 +4062,7 @@ class OptionModel extends Model {
4214
4062
  * @returns {string}
4215
4063
  */
4216
4064
  get text() {
4217
- const raw = this.dataset?.mask ?? this.targetElement?.text ?? "";
4065
+ const raw = this.targetElement?.text ?? this.dataset?.mask ?? "";
4218
4066
  const translated = Libs.tagTranslate(raw);
4219
4067
  return this.options.allowHtml ? translated : Libs.stripHtml(translated);
4220
4068
  }
@@ -4227,7 +4075,9 @@ class OptionModel extends Model {
4227
4075
  * @returns {string}
4228
4076
  */
4229
4077
  get textContent() {
4230
- return this.options.allowHtml ? Libs.stripHtml(this.text).trim() : this.text.trim();
4078
+ return this.options.allowHtml
4079
+ ? Libs.stripHtml(this.text).trim()
4080
+ : this.text.trim();
4231
4081
  }
4232
4082
  /**
4233
4083
  * Dataset object of the backing `<option>` element.
@@ -4408,8 +4258,6 @@ class ModelManager extends Lifecycle {
4408
4258
  constructor(options) {
4409
4259
  super();
4410
4260
  this.privModelList = [];
4411
- this.privAdapterHandle = null;
4412
- this.privRecyclerViewHandle = null;
4413
4261
  this.options = null;
4414
4262
  this.oldPosition = 0;
4415
4263
  this.options = options;
@@ -4461,7 +4309,9 @@ class ModelManager extends Lifecycle {
4461
4309
  else if (data.tagName === "OPTION") {
4462
4310
  const optionModel = new OptionModel(this.options, data);
4463
4311
  const parentGroup = data["__parentGroup"];
4464
- if (parentGroup && currentGroup && parentGroup === currentGroup.targetElement) {
4312
+ if (parentGroup &&
4313
+ currentGroup &&
4314
+ parentGroup === currentGroup.targetElement) {
4465
4315
  currentGroup.addItem(optionModel);
4466
4316
  optionModel.group = currentGroup;
4467
4317
  }
@@ -4886,44 +4736,10 @@ class AccessoryBox extends Lifecycle {
4886
4736
  /**
4887
4737
  * Creates an AccessoryBox and optionally initializes it with configuration.
4888
4738
  *
4889
- * @param {SelectiveOptions | null} [options=null] - Configuration controlling placement/visibility and texts.
4739
+ * @param {SelectiveOptions} [options=null] - Configuration controlling placement/visibility and texts.
4890
4740
  */
4891
- constructor(options = null) {
4741
+ constructor(options) {
4892
4742
  super();
4893
- /**
4894
- * Mounted structure returned by the node mounting helper.
4895
- * Contains the root element (`view`) and any tag handles (if present).
4896
- */
4897
- this.nodeMounted = null;
4898
- /**
4899
- * Root DOM element of the accessory box (hidden by default).
4900
- * Created during {@link init} and removed during {@link destroy}.
4901
- */
4902
- this.node = null;
4903
- /**
4904
- * Component configuration (texts, behavior, placement).
4905
- * This component reads:
4906
- * - `accessoryStyle` ("top" or default bottom)
4907
- * - `accessoryVisible` (enable/disable)
4908
- * - `multiple` (multi-select mode)
4909
- * - `textAccessoryDeselect` (a11y label prefix)
4910
- */
4911
- this.options = null;
4912
- /**
4913
- * The Select UI mask element used as the positioning reference.
4914
- * Provided by {@link setRoot}.
4915
- */
4916
- this.selectUIMask = null;
4917
- /**
4918
- * Parent container that hosts both the Select UI mask and the accessory box.
4919
- * Computed from `selectUIMask.parentElement`.
4920
- */
4921
- this.parentMask = null;
4922
- /**
4923
- * ModelManager used to run selection pipelines and coordinate state updates.
4924
- * This component does not own selection state; it delegates to the model layer.
4925
- */
4926
- this.modelManager = null;
4927
4743
  /**
4928
4744
  * Current selected option models rendered as chips.
4929
4745
  * This is a cached snapshot used for show/hide decisions and re-rendering.
@@ -5231,15 +5047,6 @@ class SearchController extends Lifecycle {
5231
5047
  */
5232
5048
  constructor(selectElement, modelManager, selectBox) {
5233
5049
  super();
5234
- /**
5235
- * AJAX configuration; when `null`, {@link search} falls back to local filtering.
5236
- * @see {@link setAjax}
5237
- */
5238
- this.ajaxConfig = null;
5239
- /** Abort handle used to cancel an in-flight AJAX request when a newer request starts. */
5240
- this.abortController = null;
5241
- /** Optional popup handle used for showing/hiding loading UI during remote operations. */
5242
- this.popup = null;
5243
5050
  /**
5244
5051
  * SelectBox handle used by custom data builder functions that require Selective context.
5245
5052
  * NOTE: This is a reference; the controller does not own/destroy the SelectBox.
@@ -5303,7 +5110,11 @@ class SearchController extends Lifecycle {
5303
5110
  */
5304
5111
  async loadByValues(values) {
5305
5112
  if (!this.ajaxConfig) {
5306
- return { success: false, items: [], message: "Ajax not configured" };
5113
+ return {
5114
+ success: false,
5115
+ items: [],
5116
+ message: "Ajax not configured",
5117
+ };
5307
5118
  }
5308
5119
  const valuesArray = Array.isArray(values) ? values : [values];
5309
5120
  if (valuesArray.length === 0)
@@ -5320,7 +5131,7 @@ class SearchController extends Lifecycle {
5320
5131
  load_by_values: "1",
5321
5132
  ...(typeof cfg.data === "function"
5322
5133
  ? cfg.data.bind(this.selectBox.Selective.find(this.selectBox.container.targetElement))("", 0)
5323
- : cfg.data ?? {}),
5134
+ : (cfg.data ?? {})),
5324
5135
  };
5325
5136
  }
5326
5137
  let response;
@@ -5330,7 +5141,9 @@ class SearchController extends Lifecycle {
5330
5141
  response = await fetch(cfg.url, {
5331
5142
  method: "POST",
5332
5143
  body: formData,
5333
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
5144
+ headers: {
5145
+ "Content-Type": "application/x-www-form-urlencoded",
5146
+ },
5334
5147
  });
5335
5148
  }
5336
5149
  else {
@@ -5367,7 +5180,7 @@ class SearchController extends Lifecycle {
5367
5180
  * Configures AJAX settings used for remote searching and pagination.
5368
5181
  * Setting `null` disables AJAX mode and causes {@link search} to use local filtering.
5369
5182
  *
5370
- * @param {AjaxConfig | null} config - AJAX configuration (endpoint, method, data builders, keepSelected, ...).
5183
+ * @param {AjaxConfig} config - AJAX configuration (endpoint, method, data builders, keepSelected, ...).
5371
5184
  * @returns {void}
5372
5185
  */
5373
5186
  setAjax(config) {
@@ -5557,7 +5370,12 @@ class SearchController extends Lifecycle {
5557
5370
  payload.selectedValue = selectedValues;
5558
5371
  }
5559
5372
  else {
5560
- payload = { search: keyword, page, selectedValue: selectedValues, ...(cfg.data ?? {}) };
5373
+ payload = {
5374
+ search: keyword,
5375
+ page,
5376
+ selectedValue: selectedValues,
5377
+ ...(cfg.data ?? {}),
5378
+ };
5561
5379
  }
5562
5380
  try {
5563
5381
  let response;
@@ -5567,13 +5385,17 @@ class SearchController extends Lifecycle {
5567
5385
  response = await fetch(cfg.url, {
5568
5386
  method: "POST",
5569
5387
  body: formData,
5570
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
5388
+ headers: {
5389
+ "Content-Type": "application/x-www-form-urlencoded",
5390
+ },
5571
5391
  signal: this.abortController.signal,
5572
5392
  });
5573
5393
  }
5574
5394
  else {
5575
5395
  const params = new URLSearchParams(payload).toString();
5576
- response = await fetch(`${cfg.url}?${params}`, { signal: this.abortController.signal });
5396
+ response = await fetch(`${cfg.url}?${params}`, {
5397
+ signal: this.abortController.signal,
5398
+ });
5577
5399
  }
5578
5400
  const data = await response.json();
5579
5401
  const result = this.parseResponse(data);
@@ -5647,7 +5469,7 @@ class SearchController extends Lifecycle {
5647
5469
  hasPagination = true;
5648
5470
  page = parseInt(data.page ?? 0, 10);
5649
5471
  totalPages = parseInt(data.totalPages ?? data.total_page ?? 1, 10);
5650
- hasMore = data.hasMore ?? (page < totalPages - 1);
5472
+ hasMore = data.hasMore ?? page < totalPages - 1;
5651
5473
  }
5652
5474
  }
5653
5475
  else if (Array.isArray(data)) {
@@ -5658,23 +5480,39 @@ class SearchController extends Lifecycle {
5658
5480
  if (data.pagination) {
5659
5481
  hasPagination = true;
5660
5482
  page = parseInt(data.pagination.page ?? 0, 10);
5661
- totalPages = parseInt(data.pagination.totalPages ?? data.pagination.total_page ?? 1, 10);
5662
- hasMore = data.pagination.hasMore ?? (page < totalPages - 1);
5483
+ totalPages = parseInt(data.pagination.totalPages ??
5484
+ data.pagination.total_page ??
5485
+ 1, 10);
5486
+ hasMore = data.pagination.hasMore ?? page < totalPages - 1;
5663
5487
  }
5664
5488
  }
5665
5489
  const normalized = items.map((item) => {
5666
- if (item instanceof HTMLOptionElement || item instanceof HTMLOptGroupElement)
5490
+ if (item instanceof HTMLOptionElement ||
5491
+ item instanceof HTMLOptGroupElement)
5667
5492
  return item;
5668
- if (item.type === "optgroup" || item.isGroup || item.group || item.label) {
5493
+ if (item.type === "optgroup" ||
5494
+ item.isGroup ||
5495
+ item.group ||
5496
+ item.label) {
5669
5497
  const label = item.label ?? item.name ?? item.title ?? "";
5670
5498
  const dataObj = item.data ?? {};
5671
5499
  const opts = (item.options ?? item.items ?? []).map((opt) => ({
5672
5500
  value: opt.value ?? opt.id ?? opt.key ?? "",
5673
- text: opt.text ?? opt.label ?? opt.name ?? opt.title ?? "",
5501
+ text: opt.text ??
5502
+ opt.label ??
5503
+ opt.name ??
5504
+ opt.title ??
5505
+ "",
5674
5506
  selected: opt.selected ?? false,
5675
- data: opt.data ?? (opt.imgsrc ? { imgsrc: opt.imgsrc } : {}),
5507
+ data: opt.data ??
5508
+ (opt.imgsrc ? { imgsrc: opt.imgsrc } : {}),
5676
5509
  }));
5677
- return { type: "optgroup", label, data: dataObj, options: opts };
5510
+ return {
5511
+ type: "optgroup",
5512
+ label,
5513
+ data: dataObj,
5514
+ options: opts,
5515
+ };
5678
5516
  }
5679
5517
  const dataObj = item.data ?? {};
5680
5518
  if (item?.imgsrc)
@@ -5716,9 +5554,12 @@ class SearchController extends Lifecycle {
5716
5554
  select.innerHTML = "";
5717
5555
  items.forEach((item) => {
5718
5556
  // Skip empty item (defensive guard)
5719
- if ((item["type"] === "option" || !item["type"]) && item["value"] === "" && item["text"] === "")
5557
+ if ((item["type"] === "option" || !item["type"]) &&
5558
+ item["value"] === "" &&
5559
+ item["text"] === "")
5720
5560
  return;
5721
- if (item instanceof HTMLOptionElement || item instanceof HTMLOptGroupElement) {
5561
+ if (item instanceof HTMLOptionElement ||
5562
+ item instanceof HTMLOptGroupElement) {
5722
5563
  select.appendChild(item);
5723
5564
  return;
5724
5565
  }
@@ -5740,7 +5581,8 @@ class SearchController extends Lifecycle {
5740
5581
  option.dataset[key] = String(opt.data[key]);
5741
5582
  });
5742
5583
  }
5743
- if (opt.selected || (keepSelected && oldSelected.includes(option.value))) {
5584
+ if (opt.selected ||
5585
+ (keepSelected && oldSelected.includes(option.value))) {
5744
5586
  option.selected = true;
5745
5587
  }
5746
5588
  optgroup.appendChild(option);
@@ -5757,7 +5599,8 @@ class SearchController extends Lifecycle {
5757
5599
  option.dataset[key] = String(item.data[key]);
5758
5600
  });
5759
5601
  }
5760
- if (item.selected || (keepSelected && oldSelected.includes(option.value))) {
5602
+ if (item.selected ||
5603
+ (keepSelected && oldSelected.includes(option.value))) {
5761
5604
  option.selected = true;
5762
5605
  }
5763
5606
  select.appendChild(option);
@@ -5833,16 +5676,6 @@ class SelectObserver {
5833
5676
  * @param {HTMLSelectElement} select - The `<select>` element to observe for mutations.
5834
5677
  */
5835
5678
  constructor(select) {
5836
- /**
5837
- * Debounce timer handle for batching rapid mutations.
5838
- *
5839
- * - Cleared and reset on each mutation event.
5840
- * - Invokes {@link handleChange} after {@link _DEBOUNCE_DELAY} milliseconds of inactivity.
5841
- * - Nulled during {@link disconnect}.
5842
- *
5843
- * @private
5844
- */
5845
- this.debounceTimer = null;
5846
5679
  /**
5847
5680
  * Debounce delay in milliseconds.
5848
5681
  *
@@ -5983,16 +5816,12 @@ class DatasetObserver {
5983
5816
  * @param element - The element whose `data-*` attributes will be observed.
5984
5817
  */
5985
5818
  constructor(element) {
5986
- /**
5987
- * Debounce timer handle for coalescing rapid attribute mutations.
5988
- * Cleared/replaced whenever a new relevant mutation arrives within the debounce window.
5989
- */
5990
- this.debounceTimer = null;
5991
5819
  this.element = element;
5992
5820
  this.observer = new MutationObserver((mutations) => {
5993
5821
  let datasetChanged = false;
5994
5822
  for (const mutation of mutations) {
5995
- if (mutation.type === "attributes" && mutation.attributeName?.startsWith("data-")) {
5823
+ if (mutation.type === "attributes" &&
5824
+ mutation.attributeName?.startsWith("data-")) {
5996
5825
  datasetChanged = true;
5997
5826
  break;
5998
5827
  }
@@ -6141,6 +5970,16 @@ class Adapter extends Lifecycle {
6141
5970
  * This flag is intentionally generic and is coordinated by higher-level components.
6142
5971
  */
6143
5972
  this.isSkipEvent = false;
5973
+ /**
5974
+ * Tracks all scheduler keys registered by this adapter instance via
5975
+ * {@link onPropChanging} and {@link onPropChanged}.
5976
+ *
5977
+ * Used during {@link destroy} to clean up all associated pipelines
5978
+ * from the global {@link Libs.callbackScheduler}.
5979
+ *
5980
+ * Keys are deduplicated automatically by Set semantics.
5981
+ */
5982
+ this.callbackSchedulerList = new Set();
6144
5983
  this.items = items;
6145
5984
  this.init();
6146
5985
  }
@@ -6182,7 +6021,9 @@ class Adapter extends Lifecycle {
6182
6021
  * @see {@link changingProp}
6183
6022
  */
6184
6023
  onPropChanging(propName, callback) {
6185
- Libs.callbackScheduler.on(`${propName}ing_${this.adapterKey}`, callback, { debounce: 0 });
6024
+ const key = `${propName}ing_${this.adapterKey}`;
6025
+ Libs.callbackScheduler.on(key, callback, { debounce: 0 });
6026
+ this.callbackSchedulerList.add(key);
6186
6027
  }
6187
6028
  /**
6188
6029
  * Registers a **post-change** callback for a property pipeline.
@@ -6198,7 +6039,11 @@ class Adapter extends Lifecycle {
6198
6039
  * @see {@link changeProp}
6199
6040
  */
6200
6041
  onPropChanged(propName, callback) {
6201
- Libs.callbackScheduler.on(`${propName}_${this.adapterKey}`, callback, { debounce: 0 });
6042
+ const key = `${propName}_${this.adapterKey}`;
6043
+ Libs.callbackScheduler.on(key, callback, {
6044
+ debounce: 0,
6045
+ });
6046
+ this.callbackSchedulerList.add(key);
6202
6047
  }
6203
6048
  /**
6204
6049
  * Triggers the **post-change** pipeline for a given property.
@@ -6233,7 +6078,7 @@ class Adapter extends Lifecycle {
6233
6078
  *
6234
6079
  * @param {HTMLElement} parent - Container element that will host the viewer.
6235
6080
  * @param {TItem} item - The model for which the viewer is created.
6236
- * @returns {TViewer | null} The created viewer instance; `null` by default.
6081
+ * @returns {TViewer} The created viewer instance; `null` by default.
6237
6082
  */
6238
6083
  viewHolder(parent, item) {
6239
6084
  return null;
@@ -6339,11 +6184,16 @@ class Adapter extends Lifecycle {
6339
6184
  if (this.is(LifecycleState.DESTROYED)) {
6340
6185
  return;
6341
6186
  }
6187
+ this.callbackSchedulerList.forEach((key) => {
6188
+ Libs.callbackScheduler.off(key);
6189
+ });
6190
+ this.callbackSchedulerList.clear();
6342
6191
  this.recyclerView = null;
6343
- this.items.forEach(item => {
6192
+ this.items.forEach((item) => {
6344
6193
  item?.destroy?.();
6345
6194
  });
6346
6195
  this.items = [];
6196
+ super.destroy();
6347
6197
  }
6348
6198
  }
6349
6199
 
@@ -6395,20 +6245,6 @@ class View extends Lifecycle {
6395
6245
  */
6396
6246
  constructor(parent) {
6397
6247
  super();
6398
- /**
6399
- * Host container element into which this view's root element is rendered/attached.
6400
- *
6401
- * This reference is captured at construction time and cleared on {@link destroy}.
6402
- */
6403
- this.parent = null;
6404
- /**
6405
- * Mounted view result containing:
6406
- * - `view`: the root element of this view
6407
- * - `tags`: a strongly-typed map of child elements for fast access
6408
- *
6409
- * This is expected to be assigned by subclasses (or a mount helper) before {@link getView} is called.
6410
- */
6411
- this.view = null;
6412
6248
  this.parent = parent;
6413
6249
  this.init();
6414
6250
  }
@@ -6509,29 +6345,6 @@ class GroupView extends View {
6509
6345
  */
6510
6346
  constructor(parent, options) {
6511
6347
  super(parent);
6512
- /**
6513
- * Strongly-typed reference to the mounted group view structure.
6514
- *
6515
- * Structure:
6516
- * - **view**: Root container element.
6517
- * - **tags**: Named references to header and items container.
6518
- *
6519
- * Lifecycle:
6520
- * - `null` until {@link mount} completes.
6521
- * - Cleared during {@link destroy}.
6522
- *
6523
- * @public
6524
- */
6525
- this.view = null;
6526
- /**
6527
- * Parsed configuration (bound from the `<select>` element via binder map).
6528
- *
6529
- * Provides feature flags (multiple/disabled/readonly/visible/virtualScroll/ajax/autoclose…),
6530
- * a11y ids (e.g. `SEID_LIST`, `SEID_HOLDER`) and user callbacks under `options.on`.
6531
- *
6532
- * @internal
6533
- */
6534
- this.options = null;
6535
6348
  this.options = options;
6536
6349
  }
6537
6350
  /**
@@ -6625,10 +6438,10 @@ class GroupView extends View {
6625
6438
  * - Safe to call multiple times with same value (idempotent).
6626
6439
  *
6627
6440
  * @public
6628
- * @param {string | null} [label=null] - New label to display; `null` preserves current label.
6441
+ * @param {string} [label=null] - New label to display; `null` preserves current label.
6629
6442
  * @returns {void}
6630
6443
  */
6631
- updateLabel(label = null) {
6444
+ updateLabel(label) {
6632
6445
  if (!this.view)
6633
6446
  return;
6634
6447
  const headerEl = this.view.tags.GroupHeader;
@@ -6675,7 +6488,7 @@ class GroupView extends View {
6675
6488
  if (!this.view)
6676
6489
  return;
6677
6490
  const items = this.view.tags.GroupItems;
6678
- const visibleItems = Array.from(items.children).filter(child => !child.classList.contains("hide"));
6491
+ const visibleItems = Array.from(items.children).filter((child) => !child.classList.contains("hide"));
6679
6492
  this.view.view.classList.toggle("hide", visibleItems.length === 0);
6680
6493
  }
6681
6494
  /**
@@ -6789,57 +6602,6 @@ class OptionView extends View {
6789
6602
  */
6790
6603
  constructor(parent, options) {
6791
6604
  super(parent);
6792
- /**
6793
- * Strongly-typed reference to the mounted option view structure.
6794
- *
6795
- * Structure:
6796
- * - **view**: Root container element.
6797
- * - **tags**: Named references to input, image (conditional), label, label content.
6798
- *
6799
- * Lifecycle:
6800
- * - `null` until {@link mount} completes.
6801
- * - Cleared during {@link destroy}.
6802
- *
6803
- * @public
6804
- */
6805
- this.view = null;
6806
- /**
6807
- * Parsed configuration (bound from the `<select>` element via binder map).
6808
- *
6809
- * Provides feature flags (multiple/disabled/readonly/visible/virtualScroll/ajax/autoclose…),
6810
- * a11y ids (e.g. `SEID_LIST`, `SEID_HOLDER`) and user callbacks under `options.on`.
6811
- *
6812
- * @internal
6813
- */
6814
- this.options = null;
6815
- /**
6816
- * Internal configuration object (Proxy target).
6817
- *
6818
- * Lifecycle:
6819
- * - Initialized during {@link initialize} with default values.
6820
- * - Mutated via {@link configProxy} Proxy trap.
6821
- *
6822
- * Notes:
6823
- * - **Should not be mutated directly**; use {@link configProxy} or typed setters.
6824
- * - Contains default values for layout, image, and alignment.
6825
- *
6826
- * @private
6827
- */
6828
- this.config = null;
6829
- /**
6830
- * Reactive Proxy wrapper around {@link config}.
6831
- *
6832
- * Behavior:
6833
- * - Intercepts property assignments via `set` trap.
6834
- * - Triggers {@link applyPartialChange} for diffed values when {@link isRendered} is `true`.
6835
- * - Prevents redundant DOM updates when value hasn't changed.
6836
- *
6837
- * Usage:
6838
- * - Accessed via {@link optionConfig} getter or typed setters ({@link isMultiple}, {@link hasImage}).
6839
- *
6840
- * @private
6841
- */
6842
- this.configProxy = null;
6843
6605
  /**
6844
6606
  * Flag indicating whether the initial render has completed.
6845
6607
  *
@@ -7004,24 +6766,30 @@ class OptionView extends View {
7004
6766
  * - Each changed property triggers {@link applyPartialChange} individually.
7005
6767
  *
7006
6768
  * @public
7007
- * @param {OptionConfigPatch | null} config - Partial configuration patch; `null` is no-op.
6769
+ * @param {OptionConfigPatch} config - Partial configuration patch; `null` is no-op.
7008
6770
  * @returns {void}
7009
6771
  */
7010
6772
  set optionConfig(config) {
7011
6773
  if (!config || !this.configProxy || !this.config)
7012
6774
  return;
7013
6775
  const changes = {};
7014
- if (config.imageWidth !== undefined && config.imageWidth !== this.config.imageWidth)
6776
+ if (config.imageWidth !== undefined &&
6777
+ config.imageWidth !== this.config.imageWidth)
7015
6778
  changes.imageWidth = config.imageWidth;
7016
- if (config.imageHeight !== undefined && config.imageHeight !== this.config.imageHeight)
6779
+ if (config.imageHeight !== undefined &&
6780
+ config.imageHeight !== this.config.imageHeight)
7017
6781
  changes.imageHeight = config.imageHeight;
7018
- if (config.imageBorderRadius !== undefined && config.imageBorderRadius !== this.config.imageBorderRadius)
6782
+ if (config.imageBorderRadius !== undefined &&
6783
+ config.imageBorderRadius !== this.config.imageBorderRadius)
7019
6784
  changes.imageBorderRadius = config.imageBorderRadius;
7020
- if (config.imagePosition !== undefined && config.imagePosition !== this.config.imagePosition)
6785
+ if (config.imagePosition !== undefined &&
6786
+ config.imagePosition !== this.config.imagePosition)
7021
6787
  changes.imagePosition = config.imagePosition;
7022
- if (config.labelValign !== undefined && config.labelValign !== this.config.labelValign)
6788
+ if (config.labelValign !== undefined &&
6789
+ config.labelValign !== this.config.labelValign)
7023
6790
  changes.labelValign = config.labelValign;
7024
- if (config.labelHalign !== undefined && config.labelHalign !== this.config.labelHalign)
6791
+ if (config.labelHalign !== undefined &&
6792
+ config.labelHalign !== this.config.labelHalign)
7025
6793
  changes.labelHalign = config.labelHalign;
7026
6794
  if (Object.keys(changes).length > 0) {
7027
6795
  Object.assign(this.configProxy, changes);
@@ -7195,9 +6963,11 @@ class OptionView extends View {
7195
6963
  case "imageBorderRadius": {
7196
6964
  const img = v.tags?.OptionImage;
7197
6965
  if (img) {
7198
- const styleProp = prop === "imageWidth" ? "width" :
7199
- prop === "imageHeight" ? "height" :
7200
- "borderRadius";
6966
+ const styleProp = prop === "imageWidth"
6967
+ ? "width"
6968
+ : prop === "imageHeight"
6969
+ ? "height"
6970
+ : "borderRadius";
7201
6971
  img.style[styleProp] = String(newValue);
7202
6972
  }
7203
6973
  break;
@@ -7318,15 +7088,6 @@ class MixedAdapter extends Adapter {
7318
7088
  super(items);
7319
7089
  /** Whether the adapter operates in multi-selection mode. */
7320
7090
  this.isMultiple = false;
7321
- /**
7322
- * Parsed configuration (bound from the `<select>` element via binder map).
7323
- *
7324
- * Provides feature flags (multiple/disabled/readonly/visible/virtualScroll/ajax/autoclose…),
7325
- * a11y ids (e.g. `SEID_LIST`, `SEID_HOLDER`) and user callbacks under `options.on`.
7326
- *
7327
- * @internal
7328
- */
7329
- this.options = null;
7330
7091
  /**
7331
7092
  * Subscribers for aggregated visibility statistics.
7332
7093
  * Fired via a debounced scheduler to avoid repeated recomputation during batch updates.
@@ -7337,11 +7098,6 @@ class MixedAdapter extends Adapter {
7337
7098
  * `-1` indicates "no highlight".
7338
7099
  */
7339
7100
  this.currentHighlightIndex = -1;
7340
- /**
7341
- * Cached pointer to the selected option in single-select mode.
7342
- * Used to efficiently clear previous selection when selecting a new option.
7343
- */
7344
- this.selectedItemSingle = null;
7345
7101
  /** Top-level group models (if any). */
7346
7102
  this.groups = [];
7347
7103
  /**
@@ -7430,7 +7186,7 @@ class MixedAdapter extends Adapter {
7430
7186
  * - Performs one-time listener binding guarded by `item.isInit`.
7431
7187
  *
7432
7188
  * @param {MixedItem} item - {@link GroupModel} or {@link OptionModel}.
7433
- * @param {GroupView | OptionView | null} viewer - The view instance that will render the model.
7189
+ * @param {GroupView | OptionView} viewer - The view instance that will render the model.
7434
7190
  * @param {number} position - Position in the top-level mixed list.
7435
7191
  * @returns {void}
7436
7192
  * @override
@@ -7657,7 +7413,7 @@ class MixedAdapter extends Adapter {
7657
7413
  return;
7658
7414
  }
7659
7415
  Libs.callbackScheduler.clear(`sche_vis_${this.adapterKey}`);
7660
- this.groups.forEach(group => {
7416
+ this.groups.forEach((group) => {
7661
7417
  group.destroy();
7662
7418
  });
7663
7419
  this.visibilityChangedCallbacks = [];
@@ -7737,7 +7493,7 @@ class MixedAdapter extends Adapter {
7737
7493
  * @returns {void}
7738
7494
  */
7739
7495
  resetHighlight() {
7740
- this.setHighlight(0);
7496
+ this.setHighlight(0, false);
7741
7497
  }
7742
7498
  /**
7743
7499
  * Moves highlight among **visible** options and optionally scrolls the new target into view.
@@ -7780,7 +7536,8 @@ class MixedAdapter extends Adapter {
7780
7536
  * @returns {void}
7781
7537
  */
7782
7538
  selectHighlighted() {
7783
- if (this.currentHighlightIndex > -1 && this.flatOptions[this.currentHighlightIndex]) {
7539
+ if (this.currentHighlightIndex > -1 &&
7540
+ this.flatOptions[this.currentHighlightIndex]) {
7784
7541
  const item = this.flatOptions[this.currentHighlightIndex];
7785
7542
  if (item.visible) {
7786
7543
  const viewEl = item.view?.getView?.();
@@ -7817,7 +7574,8 @@ class MixedAdapter extends Adapter {
7817
7574
  else {
7818
7575
  index = 0;
7819
7576
  }
7820
- if (this.currentHighlightIndex > -1 && this.flatOptions[this.currentHighlightIndex]) {
7577
+ if (this.currentHighlightIndex > -1 &&
7578
+ this.flatOptions[this.currentHighlightIndex]) {
7821
7579
  this.flatOptions[this.currentHighlightIndex].highlighted = false;
7822
7580
  }
7823
7581
  for (let i = index; i < this.flatOptions.length; i++) {
@@ -7828,12 +7586,13 @@ class MixedAdapter extends Adapter {
7828
7586
  this.currentHighlightIndex = i;
7829
7587
  if (isScrollToView) {
7830
7588
  const el = item.view?.getView?.();
7831
- if (el) {
7832
- el.scrollIntoView({ block: 'center', behavior: 'smooth' });
7589
+ if (el?.isConnected) {
7590
+ el.scrollIntoView({ block: "center", behavior: "smooth" });
7833
7591
  }
7834
7592
  else {
7835
- // If virtualized, ensure the item is rendered before trying to scroll.
7836
- this.recyclerView?.ensureRendered?.(i, { scrollIntoView: true });
7593
+ this.recyclerView?.ensureRendered?.(i, {
7594
+ scrollIntoView: true,
7595
+ });
7837
7596
  }
7838
7597
  }
7839
7598
  this.onHighlightChange(i, item.view?.getView?.()?.id);
@@ -8062,9 +7821,9 @@ class VirtualRecyclerView extends RecyclerView {
8062
7821
  *
8063
7822
  * Note: The virtualization scaffold is built when an adapter is set via {@link setAdapter}.
8064
7823
  *
8065
- * @param {HTMLDivElement | null} [viewElement=null] - Optional root container for the recycler view.
7824
+ * @param {HTMLDivElement} [viewElement=null] - Optional root container for the recycler view.
8066
7825
  */
8067
- constructor(viewElement = null) {
7826
+ constructor(viewElement) {
8068
7827
  super(viewElement);
8069
7828
  /**
8070
7829
  * Virtualization settings (materialized to `Required<VirtualOptions>`).
@@ -8100,15 +7859,18 @@ class VirtualRecyclerView extends RecyclerView {
8100
7859
  this.start = 0;
8101
7860
  /** Current window end (inclusive). -1 means not initialized. */
8102
7861
  this.end = -1;
8103
- /** Pending animation frame ids for window and measurement. */
8104
- this.rafId = null;
8105
- this.measureRaf = null;
8106
7862
  /** Re-entrancy/suspension flags used to prevent feedback loops. */
8107
7863
  this.updating = false;
8108
7864
  this.suppressResize = false;
8109
7865
  this.lastRenderCount = 0;
8110
7866
  this.suspended = false;
8111
7867
  this.resumeResizeAfter = false;
7868
+ /**
7869
+ * When set, scrollToIndex() will be called after the next measureVisibleAndUpdate()
7870
+ * completes and Fenwick has been updated with real heights.
7871
+ * Set by ensureRendered() and cleared after the corrective scroll fires.
7872
+ */
7873
+ this.pendingScrollToIndex = null;
8112
7874
  /** Small cache for sticky header height (≈16ms TTL) to limit layout reads. */
8113
7875
  this.stickyCacheTick = 0;
8114
7876
  this.stickyCacheVal = 0;
@@ -8157,20 +7919,29 @@ class VirtualRecyclerView extends RecyclerView {
8157
7919
  return;
8158
7920
  this.viewElement.replaceChildren();
8159
7921
  const nodeMounted = Libs.mountNode({
8160
- PadTop: { tag: { node: "div", classList: "seui-virtual-pad-top" } },
8161
- ItemsHost: { tag: { node: "div", classList: "seui-virtual-items" } },
8162
- PadBottom: { tag: { node: "div", classList: "seui-virtual-pad-bottom" } },
7922
+ PadTop: {
7923
+ tag: { node: "div", classList: "seui-virtual-pad-top" },
7924
+ },
7925
+ ItemsHost: {
7926
+ tag: { node: "div", classList: "seui-virtual-items" },
7927
+ },
7928
+ PadBottom: {
7929
+ tag: { node: "div", classList: "seui-virtual-pad-bottom" },
7930
+ },
8163
7931
  }, this.viewElement);
8164
7932
  this.PadTop = nodeMounted.PadTop;
8165
7933
  this.ItemsHost = nodeMounted.ItemsHost;
8166
7934
  this.PadBottom = nodeMounted.PadBottom;
8167
- this.scrollEl = this.opts.scrollEl
8168
- ?? this.viewElement.closest(".seui-popup")
8169
- ?? this.viewElement.parentElement;
7935
+ this.scrollEl =
7936
+ this.opts.scrollEl ??
7937
+ this.viewElement.closest(".seui-popup") ??
7938
+ this.viewElement.parentElement;
8170
7939
  if (!this.scrollEl)
8171
7940
  throw new Error("VirtualRecyclerView: scrollEl not found");
8172
7941
  this.boundOnScroll = this.onScroll.bind(this);
8173
- this.scrollEl.addEventListener("scroll", this.boundOnScroll, { passive: true });
7942
+ this.scrollEl.addEventListener("scroll", this.boundOnScroll, {
7943
+ passive: true,
7944
+ });
8174
7945
  this.refresh(false);
8175
7946
  this.attachResizeObserverOnce();
8176
7947
  adapter?.onVisibilityChanged?.(() => this.refreshItem());
@@ -8209,7 +7980,9 @@ class VirtualRecyclerView extends RecyclerView {
8209
7980
  resume() {
8210
7981
  this.suspended = false;
8211
7982
  if (this.scrollEl && this.boundOnScroll) {
8212
- this.scrollEl.addEventListener("scroll", this.boundOnScroll, { passive: true });
7983
+ this.scrollEl.addEventListener("scroll", this.boundOnScroll, {
7984
+ passive: true,
7985
+ });
8213
7986
  }
8214
7987
  if (this.resumeResizeAfter) {
8215
7988
  this.attachResizeObserverOnce();
@@ -8264,9 +8037,19 @@ class VirtualRecyclerView extends RecyclerView {
8264
8037
  * @returns {void}
8265
8038
  */
8266
8039
  ensureRendered(index, opt) {
8267
- this.mountRange(index, index);
8268
- if (opt?.scrollIntoView)
8269
- this.scrollToIndex(index);
8040
+ if (!opt?.scrollIntoView) {
8041
+ // No scroll requested — mount only (legacy path, used by probes).
8042
+ this.mountRange(index, index);
8043
+ return;
8044
+ }
8045
+ // Pass 1: instant — brings window to vicinity, triggers measure.
8046
+ // Must be instant so Pass 2 smooth scroll isn't interrupted mid-animation.
8047
+ this.scrollToIndex(index, "instant");
8048
+ // Pass 2: measureVisibleAndUpdate() will consume this and fire a corrective
8049
+ // smooth scroll after Fenwick has been updated with real heights.
8050
+ // rv.resume() is guaranteed to run before this callback (popup.open onComplete
8051
+ // calls rv.resume() first), so the window is already rendered when we arrive here.
8052
+ this.pendingScrollToIndex = index;
8270
8053
  }
8271
8054
  /**
8272
8055
  * Scrolls the scroll container to align the item at `index` into view.
@@ -8280,15 +8063,25 @@ class VirtualRecyclerView extends RecyclerView {
8280
8063
  * @param {number} index - Item index to bring into view.
8281
8064
  * @returns {void}
8282
8065
  */
8283
- scrollToIndex(index) {
8066
+ scrollToIndex(index, behavior = "smooth") {
8284
8067
  const count = this.adapter?.itemCount?.() ?? 0;
8285
8068
  if (count <= 0)
8286
8069
  return;
8287
8070
  const topInContainer = this.offsetTopOf(index);
8288
8071
  const containerTop = this.containerTopInScroll();
8289
- const target = containerTop + topInContainer;
8072
+ const stickyH = this.stickyTopHeight();
8073
+ const viewportH = Math.max(0, this.scrollEl.clientHeight - stickyH);
8074
+ // item height from cache, or current estimate for unmeasured items
8075
+ const est = this.getEstimate();
8076
+ const itemH = this.heightCache[index] ?? est;
8077
+ // Align item center to viewport center (below any sticky header).
8078
+ // viewportH already excludes stickyH, so no further offset needed.
8079
+ // Equivalent to scrollIntoView({ block: "center" }).
8080
+ const centeredTarget = containerTop + topInContainer
8081
+ - (viewportH - itemH) / 3;
8290
8082
  const maxScroll = Math.max(0, this.scrollEl.scrollHeight - this.scrollEl.clientHeight);
8291
- this.scrollEl.scrollTop = Math.min(Math.max(0, target), maxScroll);
8083
+ const clamped = Math.min(Math.max(0, centeredTarget), maxScroll);
8084
+ this.scrollEl.scrollTo({ top: clamped, behavior });
8292
8085
  }
8293
8086
  /**
8294
8087
  * Disposes runtime resources without destroying the instance.
@@ -8307,7 +8100,7 @@ class VirtualRecyclerView extends RecyclerView {
8307
8100
  this.scrollEl.removeEventListener("scroll", this.boundOnScroll);
8308
8101
  }
8309
8102
  this.resizeObs?.disconnect();
8310
- this.created.forEach(el => el.remove());
8103
+ this.created.forEach((el) => el.remove());
8311
8104
  this.created.clear();
8312
8105
  }
8313
8106
  /**
@@ -8355,9 +8148,22 @@ class VirtualRecyclerView extends RecyclerView {
8355
8148
  if (count <= 0)
8356
8149
  return;
8357
8150
  this.suspend();
8358
- this.resetState();
8151
+ this.pendingScrollToIndex = null;
8152
+ // When visibility changes (search filter applied or cleared), heightCache may
8153
+ // contain heights measured while only a subset of items was visible. Re-using
8154
+ // these partial measurements in rebuildFenwick() causes incorrect prefix sums
8155
+ // (e.g. items measured while scrolled into a filtered window have real heights,
8156
+ // while surrounding items still use estimates — creating an uneven Fenwick).
8157
+ //
8158
+ // Safe fix: clear heightCache entirely on visibility change. The adaptive
8159
+ // estimator will re-seed from probeInitialHeight() on the next render, and
8160
+ // items will be re-measured as they scroll into view.
8161
+ this.heightCache = [];
8162
+ this.measuredSum = 0;
8163
+ this.measuredCount = 0;
8164
+ this.firstMeasured = false;
8165
+ this.resetDOM();
8359
8166
  this.cleanupInvisibleItems();
8360
- this.recomputeMeasuredStats(count);
8361
8167
  this.rebuildFenwick(count);
8362
8168
  this.start = 0;
8363
8169
  this.end = -1;
@@ -8375,7 +8181,30 @@ class VirtualRecyclerView extends RecyclerView {
8375
8181
  }
8376
8182
  }
8377
8183
  /**
8378
- * Resets internal state: mounted elements, caches, Fenwick sums, padding, and estimator stats.
8184
+ * Resets DOM nodes, Fenwick sums, padding, and estimator stats — but preserves {@link heightCache}.
8185
+ *
8186
+ * Use this inside {@link refreshItem} so that {@link recomputeMeasuredStats} can still
8187
+ * read previously measured heights before the Fenwick tree is rebuilt.
8188
+ *
8189
+ * DOM side effects:
8190
+ * - Removes all currently mounted item elements tracked in {@link created}.
8191
+ * - Resets pad heights to `0px`.
8192
+ *
8193
+ * @returns {void}
8194
+ */
8195
+ resetDOM() {
8196
+ this.created.forEach((el) => el.remove());
8197
+ this.created.clear();
8198
+ this.fenwick.reset(0);
8199
+ this.PadTop.style.height = "0px";
8200
+ this.PadBottom.style.height = "0px";
8201
+ this.firstMeasured = false;
8202
+ }
8203
+ /**
8204
+ * Full reset: clears DOM nodes, Fenwick sums, padding, estimator stats, AND {@link heightCache}.
8205
+ *
8206
+ * Use this for complete teardown (e.g., adapter swap, destroy sequence) where all
8207
+ * cached measurements should be discarded.
8379
8208
  *
8380
8209
  * DOM side effects:
8381
8210
  * - Removes all currently mounted item elements tracked in {@link created}.
@@ -8384,7 +8213,7 @@ class VirtualRecyclerView extends RecyclerView {
8384
8213
  * @returns {void}
8385
8214
  */
8386
8215
  resetState() {
8387
- this.created.forEach(el => el.remove());
8216
+ this.created.forEach((el) => el.remove());
8388
8217
  this.created.clear();
8389
8218
  this.heightCache = [];
8390
8219
  this.fenwick.reset(0);
@@ -8658,8 +8487,12 @@ class VirtualRecyclerView extends RecyclerView {
8658
8487
  el.setAttribute(VirtualRecyclerView.ATTR_INDEX, String(index));
8659
8488
  const prev = el.previousElementSibling;
8660
8489
  const next = el.nextElementSibling;
8661
- const needsReorder = (prev && Number(prev.getAttribute(VirtualRecyclerView.ATTR_INDEX)) > index) ||
8662
- (next && Number(next.getAttribute(VirtualRecyclerView.ATTR_INDEX)) < index);
8490
+ const needsReorder = (prev &&
8491
+ Number(prev.getAttribute(VirtualRecyclerView.ATTR_INDEX)) >
8492
+ index) ||
8493
+ (next &&
8494
+ Number(next.getAttribute(VirtualRecyclerView.ATTR_INDEX)) <
8495
+ index);
8663
8496
  if (needsReorder) {
8664
8497
  el.remove();
8665
8498
  this.insertIntoHostByIndex(index, el);
@@ -8681,7 +8514,10 @@ class VirtualRecyclerView extends RecyclerView {
8681
8514
  if (this.resizeObs)
8682
8515
  return;
8683
8516
  this.resizeObs = new ResizeObserver(() => {
8684
- if (this.suppressResize || this.suspended || !this.adapter || this.measureRaf != null)
8517
+ if (this.suppressResize ||
8518
+ this.suspended ||
8519
+ !this.adapter ||
8520
+ this.measureRaf != null)
8685
8521
  return;
8686
8522
  this.measureRaf = requestAnimationFrame(() => {
8687
8523
  this.measureRaf = null;
@@ -8722,6 +8558,15 @@ class VirtualRecyclerView extends RecyclerView {
8722
8558
  this.rebuildFenwick(count);
8723
8559
  this.scheduleUpdateWindow();
8724
8560
  }
8561
+ // Corrective scroll: if ensureRendered() registered a target index, fire
8562
+ // scrollToIndex() now that real heights are in Fenwick. Clear the target
8563
+ // first to prevent infinite re-triggering (scrollToIndex may cause another
8564
+ // measure cycle, but heights won't change so changed === false next time).
8565
+ if (this.pendingScrollToIndex !== null) {
8566
+ const target = this.pendingScrollToIndex;
8567
+ this.pendingScrollToIndex = null;
8568
+ this.scrollToIndex(target, "smooth");
8569
+ }
8725
8570
  }
8726
8571
  /**
8727
8572
  * Scroll event handler. Schedules a window update on the next frame.
@@ -9010,31 +8855,6 @@ class SelectBox extends Lifecycle {
9010
8855
  * @internal
9011
8856
  */
9012
8857
  this.oldValue = null;
9013
- /**
9014
- * Root wrapper DOM node for the enhanced UI.
9015
- *
9016
- * Created during {@link init} via {@link Libs.mountNode}, inserted into the DOM during {@link mount},
9017
- * and removed during {@link destroy}.
9018
- */
9019
- this.node = null;
9020
- /**
9021
- * Parsed configuration (bound from the `<select>` element via binder map).
9022
- *
9023
- * Provides feature flags (multiple/disabled/readonly/visible/virtualScroll/ajax/autoclose…),
9024
- * a11y ids (e.g. `SEID_LIST`, `SEID_HOLDER`) and user callbacks under `options.on`.
9025
- *
9026
- * @internal
9027
- */
9028
- this.options = null;
9029
- /**
9030
- * Manager that owns model resources and bridges the Adapter ↔ RecyclerView pipeline.
9031
- *
9032
- * The configured adapter is {@link MixedAdapter}. The recyclerview implementation is chosen
9033
- * based on `options.virtualScroll` (standard {@link RecyclerView} vs {@link VirtualRecyclerView}).
9034
- *
9035
- * @internal
9036
- */
9037
- this.optionModelManager = null;
9038
8858
  /**
9039
8859
  * Whether the popup/list UI is currently open.
9040
8860
  *
@@ -9066,20 +8886,10 @@ class SelectBox extends Lifecycle {
9066
8886
  * @internal
9067
8887
  */
9068
8888
  this.hasDeInitialized = false;
9069
- /**
9070
- * Selective context (global helper / registry).
9071
- *
9072
- * Used to locate the instance wrapper via `Selective.find(...)` and to close other open instances.
9073
- */
9074
- this.Selective = null;
9075
8889
  /**
9076
8890
  * Registered plugins for this SelectBox instance.
9077
8891
  */
9078
8892
  this.plugins = [];
9079
- /**
9080
- * Cached plugin context for this SelectBox instance.
9081
- */
9082
- this.pluginContext = null;
9083
8893
  if (select && Selective)
9084
8894
  this.initialize(select, Selective);
9085
8895
  }
@@ -9204,7 +9014,9 @@ class SelectBox extends Lifecycle {
9204
9014
  classList: "seui-view",
9205
9015
  tabIndex: 0,
9206
9016
  onkeydown: (e) => {
9207
- if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
9017
+ if (e.key === "Enter" ||
9018
+ e.key === " " ||
9019
+ e.key === "ArrowDown") {
9208
9020
  e.preventDefault();
9209
9021
  this.getAction()?.open();
9210
9022
  }
@@ -9310,6 +9122,7 @@ class SelectBox extends Lifecycle {
9310
9122
  e.preventDefault();
9311
9123
  });
9312
9124
  Refresher.resizeBox(select, container.tags.ViewPanel);
9125
+ Refresher.resizeBox(select, this.node, true);
9313
9126
  select.classList.add("init");
9314
9127
  // initial mask
9315
9128
  const action = this.getAction();
@@ -9596,7 +9409,11 @@ class SelectBox extends Lifecycle {
9596
9409
  get value() {
9597
9410
  const item_list = this.valueArray;
9598
9411
  const valLength = item_list.length;
9599
- return valLength > 1 ? item_list : valLength === 0 ? "" : item_list[0];
9412
+ return valLength > 1
9413
+ ? item_list
9414
+ : valLength === 0
9415
+ ? ""
9416
+ : item_list[0];
9600
9417
  },
9601
9418
  get valueArray() {
9602
9419
  const item_list = [];
@@ -9621,7 +9438,7 @@ class SelectBox extends Lifecycle {
9621
9438
  get mask() {
9622
9439
  const item_list = [];
9623
9440
  superThis.getModelOption(true).forEach((m) => {
9624
- item_list.push(m.text);
9441
+ item_list.push(m.mask);
9625
9442
  });
9626
9443
  return item_list;
9627
9444
  },
@@ -9631,7 +9448,11 @@ class SelectBox extends Lifecycle {
9631
9448
  item_list.push(m.text);
9632
9449
  });
9633
9450
  const valLength = item_list.length;
9634
- return valLength > 1 ? item_list : valLength === 0 ? "" : item_list[0];
9451
+ return valLength > 1
9452
+ ? item_list
9453
+ : valLength === 0
9454
+ ? ""
9455
+ : item_list[0];
9635
9456
  },
9636
9457
  get isOpen() {
9637
9458
  return superThis.isOpen;
@@ -9656,7 +9477,8 @@ class SelectBox extends Lifecycle {
9656
9477
  },
9657
9478
  selectAll(_evtToken, trigger = true) {
9658
9479
  if (bindedOptions.multiple && bindedOptions.maxSelected > 0) {
9659
- if (superThis.getModelOption().length > bindedOptions.maxSelected)
9480
+ if (superThis.getModelOption().length >
9481
+ bindedOptions.maxSelected)
9660
9482
  return;
9661
9483
  }
9662
9484
  if (this.disabled || this.readonly || !bindedOptions.multiple)
@@ -9688,7 +9510,7 @@ class SelectBox extends Lifecycle {
9688
9510
  },
9689
9511
  deSelectByDataset(_evtToken, dataset, trigger = true) {
9690
9512
  if (dataset) {
9691
- superThis.getModelOption().forEach(optionModel => {
9513
+ superThis.getModelOption().forEach((optionModel) => {
9692
9514
  if (optionModel.dataset) {
9693
9515
  for (let searchKey in dataset) {
9694
9516
  let value = dataset[searchKey];
@@ -9702,12 +9524,14 @@ class SelectBox extends Lifecycle {
9702
9524
  this.change(false, trigger);
9703
9525
  }
9704
9526
  },
9705
- setValue(_evtToken = null, value, trigger = true, force = false) {
9527
+ setValue(_evtToken, value, trigger = true, force = false) {
9706
9528
  if (!Array.isArray(value))
9707
9529
  value = [value];
9708
9530
  value = value.filter((v) => v !== "" && v != null);
9709
9531
  if (value.length === 0) {
9710
- superThis.getModelOption().forEach((m) => (m.selectedNonTrigger = false));
9532
+ superThis
9533
+ .getModelOption()
9534
+ .forEach((m) => (m.selectedNonTrigger = false));
9711
9535
  this.change(false, trigger);
9712
9536
  return;
9713
9537
  }
@@ -9721,17 +9545,19 @@ class SelectBox extends Lifecycle {
9721
9545
  return;
9722
9546
  // AJAX: load missing values
9723
9547
  if (container.searchController?.isAjax?.()) {
9548
+ container.searchController.resetPagination();
9549
+ superThis.hasLoadedOnce = false;
9724
9550
  const { missing } = container.searchController.checkMissingValues(value);
9725
9551
  if (missing.length > 0) {
9726
9552
  (async () => {
9727
9553
  if (bindedOptions.loadingfield)
9728
9554
  container.popup?.showLoading?.();
9729
9555
  try {
9730
- container.searchController.resetPagination();
9731
9556
  const result = await container.searchController.loadByValues(missing);
9732
9557
  if (result.success && result.items.length > 0) {
9733
9558
  result.items.forEach((it) => {
9734
- if (missing.includes(it.value) || missing.includes(it.text))
9559
+ if (missing.includes(it.value) ||
9560
+ missing.includes(it.text))
9735
9561
  it.selected = true;
9736
9562
  });
9737
9563
  container.searchController.applyAjaxResult?.(result.items, false, false);
@@ -9742,6 +9568,10 @@ class SelectBox extends Lifecycle {
9742
9568
  }
9743
9569
  else if (missing.length > 0) {
9744
9570
  console.warn(`Could not load ${missing.length} values:`, missing);
9571
+ setTimeout(() => {
9572
+ container.searchController.resetPagination();
9573
+ this.change(false, trigger);
9574
+ }, 200);
9745
9575
  }
9746
9576
  }
9747
9577
  catch (error) {
@@ -9770,7 +9600,8 @@ class SelectBox extends Lifecycle {
9770
9600
  this.change(false, trigger);
9771
9601
  },
9772
9602
  load() {
9773
- if ((!superThis.hasLoadedOnce || superThis.isBeforeSearch) && bindedOptions?.ajax) {
9603
+ if ((!superThis.hasLoadedOnce || superThis.isBeforeSearch) &&
9604
+ bindedOptions?.ajax) {
9774
9605
  container.searchController.resetPagination();
9775
9606
  container.popup.showLoading();
9776
9607
  superThis.hasLoadedOnce = true;
@@ -9816,7 +9647,13 @@ class SelectBox extends Lifecycle {
9816
9647
  adapter.resetHighlight();
9817
9648
  }
9818
9649
  this.load();
9819
- container.popup.open(null, !container.popup.loadingState.isVisible);
9650
+ container.popup.open(() => {
9651
+ setTimeout(() => {
9652
+ if (selectedOption) {
9653
+ adapter.setHighlight(selectedOption, bindedOptions.autoscroll);
9654
+ }
9655
+ }, 100);
9656
+ }, !container.popup.loadingState.isVisible);
9820
9657
  container.searchbox.show();
9821
9658
  const ViewPanel = container.tags.ViewPanel;
9822
9659
  ViewPanel.setAttribute("aria-expanded", "true");
@@ -9857,9 +9694,10 @@ class SelectBox extends Lifecycle {
9857
9694
  else
9858
9695
  this.open();
9859
9696
  },
9860
- change(_evtToken = null, canTrigger = true) {
9697
+ change(_evtToken, canTrigger = true) {
9861
9698
  if (canTrigger) {
9862
- if (bindedOptions.multiple && bindedOptions.maxSelected > 0) {
9699
+ if (bindedOptions.multiple &&
9700
+ bindedOptions.maxSelected > 0) {
9863
9701
  if (this.valueArray.length > bindedOptions.maxSelected) {
9864
9702
  this.setValue(null, this.oldValue, false, true);
9865
9703
  }
@@ -9894,7 +9732,8 @@ class SelectBox extends Lifecycle {
9894
9732
  },
9895
9733
  refreshMask() {
9896
9734
  let mask = bindedOptions.placeholder;
9897
- if (!bindedOptions.multiple && superThis.getModelOption().length > 0) {
9735
+ if (!bindedOptions.multiple &&
9736
+ superThis.getModelOption().length > 0) {
9898
9737
  mask = this.mask[0];
9899
9738
  }
9900
9739
  mask ?? (mask = bindedOptions.placeholder);
@@ -9926,7 +9765,9 @@ class SelectBox extends Lifecycle {
9926
9765
  .search("")
9927
9766
  .then(() => {
9928
9767
  container.popup?.triggerResize?.();
9929
- resove(getInstance());
9768
+ setTimeout(() => {
9769
+ resove(getInstance());
9770
+ }, 60);
9930
9771
  })
9931
9772
  .catch((err) => {
9932
9773
  console.error("Initial ajax load error:", err);
@@ -9976,7 +9817,8 @@ class SelectBox extends Lifecycle {
9976
9817
  set(value) {
9977
9818
  superThis[privateProp] = !!value;
9978
9819
  if (superThis.container?.targetElement?.dataset) {
9979
- superThis.container.targetElement.dataset[prop] = String(!!value);
9820
+ superThis.container.targetElement.dataset[prop] =
9821
+ String(!!value);
9980
9822
  }
9981
9823
  },
9982
9824
  enumerable: true,
@@ -10004,7 +9846,7 @@ class SelectBox extends Lifecycle {
10004
9846
  * @returns A flat array of option models (possibly filtered).
10005
9847
  * @internal
10006
9848
  */
10007
- getModelOption(isSelected = null) {
9849
+ getModelOption(isSelected) {
10008
9850
  if (!this.optionModelManager)
10009
9851
  return [];
10010
9852
  const { modelList } = this.optionModelManager.getResources();
@@ -10084,14 +9926,6 @@ class ElementAdditionObserver {
10084
9926
  * @internal
10085
9927
  */
10086
9928
  this.isActive = false;
10087
- /**
10088
- * Underlying DOM {@link MutationObserver} instance.
10089
- *
10090
- * `null` when disconnected.
10091
- *
10092
- * @internal
10093
- */
10094
- this.observer = null;
10095
9929
  /**
10096
9930
  * Registered detection callbacks.
10097
9931
  *
@@ -10685,7 +10519,7 @@ class Selective extends Lifecycle {
10685
10519
  if (wasObserving)
10686
10520
  this.EAObserver?.disconnect();
10687
10521
  bindMap.self?.deInit?.();
10688
- const wrapper = (bindMap.container?.element) ?? selectElement.parentElement;
10522
+ const wrapper = bindMap.container?.element ?? selectElement.parentElement;
10689
10523
  selectElement.style.display = "";
10690
10524
  selectElement.style.visibility = "";
10691
10525
  selectElement.disabled = false;
@@ -10905,7 +10739,7 @@ const SECLASS = new Selective();
10905
10739
  *
10906
10740
  * Declared as `const` literal type to enable strict typing and easy tree-shaking.
10907
10741
  */
10908
- const version = "1.4.0";
10742
+ const version = "1.4.2";
10909
10743
  /**
10910
10744
  * Library name identifier.
10911
10745
  *
@@ -10951,7 +10785,7 @@ function find(query) {
10951
10785
  * // Destroy all instances
10952
10786
  * destroy();
10953
10787
  */
10954
- function destroy(query = null) {
10788
+ function destroy(query) {
10955
10789
  SECLASS.destroy(query);
10956
10790
  }
10957
10791
  /**