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
  (function (global, factory) {
3
3
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
4
4
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
@@ -153,7 +153,7 @@
153
153
  *
154
154
  * @public
155
155
  * @param {TimerKey} key - Group identifier for callbacks.
156
- * @param {(payload: any[] | null) => void} callback - Function to execute after debounce timeout.
156
+ * @param {(payload?: any[]) => void} callback - Function to execute after debounce timeout.
157
157
  * @param {TimerOptions} [options={}] - Scheduling options (`debounce`, `once`).
158
158
  * @returns {void}
159
159
  */
@@ -241,7 +241,8 @@
241
241
  await resp;
242
242
  }
243
243
  }
244
- catch { }
244
+ catch {
245
+ }
245
246
  finally {
246
247
  if (entry.once) {
247
248
  executes[i] = undefined;
@@ -374,9 +375,11 @@
374
375
  * @param {boolean} systemNodeCreate - If true, do not clone; use original node.
375
376
  * @returns {HTMLElement} - The processed element.
376
377
  */
377
- static nodeCloner(node = document.documentElement, _nodeOption = null, systemNodeCreate = false) {
378
+ static nodeCloner(node = document.documentElement, _nodeOption, systemNodeCreate = false) {
378
379
  const nodeOption = { ...(_nodeOption ?? {}) };
379
- const element_creation = systemNodeCreate ? node : node.cloneNode(true);
380
+ const element_creation = systemNodeCreate
381
+ ? node
382
+ : node.cloneNode(true);
380
383
  const classList = nodeOption.classList;
381
384
  if (typeof classList === "string") {
382
385
  element_creation.classList.add(classList);
@@ -448,11 +451,13 @@
448
451
  * @param {TTags|Object} [recursiveTemp={}] - Accumulator for tag references.
449
452
  * @returns {TTags} - Tag map or the final mount result.
450
453
  */
451
- static mountNode(rawObj, parentE = null, isPrepend = false, isRecusive = false, recursiveTemp = {}) {
454
+ static mountNode(rawObj, parentE, isPrepend = false, isRecusive = false, recursiveTemp = {}) {
452
455
  let view = null;
453
456
  for (const key in rawObj) {
454
457
  const singleObj = rawObj[key];
455
- const tag = singleObj?.tag?.tagName ? singleObj.tag : this.nodeCreator(singleObj.tag);
458
+ const tag = singleObj?.tag?.tagName
459
+ ? singleObj.tag
460
+ : this.nodeCreator(singleObj.tag);
456
461
  recursiveTemp[key] = tag;
457
462
  if (singleObj?.child)
458
463
  this.mountNode(singleObj.child, tag, false, false, recursiveTemp);
@@ -669,7 +674,8 @@
669
674
  n.removeAttribute(name);
670
675
  return;
671
676
  }
672
- if (/^(href|src|xlink:href)$/i.test(name) && /^javascript:/i.test(value)) {
677
+ if (/^(href|src|xlink:href)$/i.test(name) &&
678
+ /^javascript:/i.test(value)) {
673
679
  n.removeAttribute(name);
674
680
  }
675
681
  }
@@ -699,7 +705,10 @@
699
705
  static string2normalize(str) {
700
706
  if (str == null)
701
707
  return "";
702
- const s = String(str).toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
708
+ const s = String(str)
709
+ .toLowerCase()
710
+ .normalize("NFD")
711
+ .replace(/[\u0300-\u036f]/g, "");
703
712
  return s.replace(/đ/g, "d").replace(/Đ/g, "d");
704
713
  }
705
714
  /**
@@ -741,7 +750,8 @@
741
750
  */
742
751
  static IsIOS() {
743
752
  const ua = navigator.userAgent;
744
- return /iP(hone|ad|od)/.test(ua) || (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
753
+ return (/iP(hone|ad|od)/.test(ua) ||
754
+ (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1));
745
755
  }
746
756
  /**
747
757
  * Converts an arbitrary CSS size value into pixel units by measuring a temporary element.
@@ -763,14 +773,16 @@
763
773
  if (v.endsWith("rem"))
764
774
  return fs * parseFloat(v) + "px";
765
775
  // fallback: DOM measure
766
- const el = this.nodeCreator({ node: "div", style: { height: v, opacity: "0" } });
776
+ const el = this.nodeCreator({
777
+ node: "div",
778
+ style: { height: v, opacity: "0" },
779
+ });
767
780
  document.body.appendChild(el);
768
781
  const px = el.offsetHeight + "px";
769
782
  el.remove();
770
783
  return px;
771
784
  }
772
785
  }
773
- Libs._iStorage = null;
774
786
  /**
775
787
  * Schedules and batches function executions keyed by name, with debounced timers.
776
788
  * Provides setExecute(), clearExecute(), and run() to manage deferred callbacks.
@@ -966,8 +978,9 @@
966
978
  *
967
979
  * @param select - Native `<select>` element used as the sizing reference and option source.
968
980
  * @param view - View panel element whose inline styles will be updated.
981
+ * @param isWidthOnly - If true, only the width will be updated; height will be left unchanged.
969
982
  */
970
- static resizeBox(select, view) {
983
+ static resizeBox(select, view, isWidthOnly = false) {
971
984
  const bindedMap = Libs.getBinderMap(select);
972
985
  if (!bindedMap?.options)
973
986
  return;
@@ -987,7 +1000,12 @@
987
1000
  width = options.width;
988
1001
  if (cfgHeight > 0)
989
1002
  height = options.height;
990
- Object.assign(view.style, { width, height, minWidth, minHeight });
1003
+ if (isWidthOnly) {
1004
+ Object.assign(view.style, { width, maxWidth: width, minWidth });
1005
+ }
1006
+ else {
1007
+ Object.assign(view.style, { width, height, maxWidth: width, minWidth, minHeight });
1008
+ }
991
1009
  }
992
1010
  }
993
1011
 
@@ -1297,25 +1315,6 @@
1297
1315
  */
1298
1316
  constructor(options) {
1299
1317
  super();
1300
- /**
1301
- * Root DOM element for the placeholder.
1302
- *
1303
- * Created during {@link initialize}. Removed from the DOM during {@link destroy}.
1304
- * `null` before initialization and after destruction.
1305
- */
1306
- this.node = null;
1307
- /**
1308
- * Configuration snapshot used to render and optionally persist placeholder content.
1309
- *
1310
- * Key fields used by this component:
1311
- * - `placeholder`: initial/current placeholder text/markup
1312
- * - `allowHtml`: controls whether HTML is rendered or stripped
1313
- *
1314
- * Cleared during {@link destroy}.
1315
- *
1316
- * @internal
1317
- */
1318
- this.options = null;
1319
1318
  if (options)
1320
1319
  this.initialize(options);
1321
1320
  }
@@ -1540,34 +1539,8 @@
1540
1539
  *
1541
1540
  * @param options - Feature flags and labels for the two actions.
1542
1541
  */
1543
- constructor(options = null) {
1542
+ constructor(options) {
1544
1543
  super();
1545
- /**
1546
- * Result returned by {@link Libs.mountNode}.
1547
- *
1548
- * Stores the mounted view structure so the component can keep a stable reference
1549
- * to its created DOM nodes. `null` before {@link initialize}.
1550
- *
1551
- * @internal
1552
- */
1553
- this.nodeMounted = null;
1554
- /**
1555
- * Root element of this control.
1556
- *
1557
- * Created during {@link initialize}. This node is used by {@link show}/{@link hide}
1558
- * and removed during {@link destroy}.
1559
- */
1560
- this.node = null;
1561
- /**
1562
- * Configuration snapshot used for:
1563
- * - labels (`textSelectAll`, `textDeselectAll`)
1564
- * - feature flags (`multiple`, `selectall`)
1565
- *
1566
- * Treated as read-only after initialization; cleared on {@link destroy}.
1567
- *
1568
- * @internal
1569
- */
1570
- this.options = null;
1571
1544
  /**
1572
1545
  * Callback list invoked when the "Select all" control is activated.
1573
1546
  *
@@ -1655,7 +1628,8 @@
1655
1628
  available() {
1656
1629
  if (!this.options)
1657
1630
  return false;
1658
- return Libs.string2Boolean(this.options.multiple) && Libs.string2Boolean(this.options.selectall);
1631
+ return (Libs.string2Boolean(this.options.multiple) &&
1632
+ Libs.string2Boolean(this.options.selectall));
1659
1633
  }
1660
1634
  /**
1661
1635
  * Re-evaluates visibility and advances the lifecycle update step.
@@ -1712,7 +1686,7 @@
1712
1686
  *
1713
1687
  * @param action - Callback invoked on activation; ignored when not a function.
1714
1688
  */
1715
- onSelectAll(action = null) {
1689
+ onSelectAll(action) {
1716
1690
  if (typeof action === "function") {
1717
1691
  this.actionOnSelectAll.push(action);
1718
1692
  }
@@ -1728,7 +1702,7 @@
1728
1702
  *
1729
1703
  * @param action - Callback invoked on activation; ignored when not a function.
1730
1704
  */
1731
- onDeSelectAll(action = null) {
1705
+ onDeSelectAll(action) {
1732
1706
  if (typeof action === "function") {
1733
1707
  this.actionOnDeSelectAll.push(action);
1734
1708
  }
@@ -1796,26 +1770,10 @@
1796
1770
  * If `options` are provided, initialization runs immediately (creates {@link node} and
1797
1771
  * transitions to `INITIALIZED`).
1798
1772
  *
1799
- * @param {SelectiveOptions | null} [options=null] - Configuration containing empty state messages.
1773
+ * @param {SelectiveOptions} [options=null] - Configuration containing empty state messages.
1800
1774
  */
1801
- constructor(options = null) {
1775
+ constructor(options) {
1802
1776
  super();
1803
- /**
1804
- * Root DOM element for the empty state UI.
1805
- *
1806
- * - Created during {@link initialize}.
1807
- * - Intended to be appended by the parent container (component does not auto-attach).
1808
- * - Removed from DOM during {@link destroy}.
1809
- */
1810
- this.node = null;
1811
- /**
1812
- * Configuration source for empty state messages.
1813
- *
1814
- * Expected to provide at least:
1815
- * - `textNoData` (for `"nodata"`)
1816
- * - `textNotFound` (for `"notfound"`)
1817
- */
1818
- this.options = null;
1819
1777
  if (options)
1820
1778
  this.initialize(options);
1821
1779
  }
@@ -1941,25 +1899,10 @@
1941
1899
  * If `options` are provided, initialization runs immediately (creates {@link node} and
1942
1900
  * transitions to `INITIALIZED`).
1943
1901
  *
1944
- * @param {SelectiveOptions | null} [options=null] - Configuration containing the loading message text.
1902
+ * @param {SelectiveOptions} [options=null] - Configuration containing the loading message text.
1945
1903
  */
1946
- constructor(options = null) {
1904
+ constructor(options) {
1947
1905
  super();
1948
- /**
1949
- * Root DOM element for the loading state UI.
1950
- *
1951
- * - Created during {@link initialize}.
1952
- * - Intended to be appended by the parent container (component does not auto-attach).
1953
- * - Removed from DOM during {@link destroy}.
1954
- */
1955
- this.node = null;
1956
- /**
1957
- * Configuration source for loading message text.
1958
- *
1959
- * Expected to provide:
1960
- * - `textLoading` (displayed while loading is active)
1961
- */
1962
- this.options = null;
1963
1906
  if (options)
1964
1907
  this.initialize(options);
1965
1908
  }
@@ -2117,27 +2060,6 @@
2117
2060
  * It does **not** indicate that observers are currently attached (see {@link connect}).
2118
2061
  */
2119
2062
  this.isInit = false;
2120
- /**
2121
- * The currently bound DOM element being observed.
2122
- *
2123
- * @remarks
2124
- * Set by {@link connect} and cleared by {@link disconnect}.
2125
- */
2126
- this.element = null;
2127
- /**
2128
- * Underlying `ResizeObserver` instance.
2129
- *
2130
- * @remarks
2131
- * Allocated on {@link connect}. Disconnected and nulled on {@link disconnect}.
2132
- */
2133
- this.resizeObserver = null;
2134
- /**
2135
- * Underlying `MutationObserver` instance watching `style` and `class` attribute changes.
2136
- *
2137
- * @remarks
2138
- * Allocated on {@link connect}. Disconnected and nulled on {@link disconnect}.
2139
- */
2140
- this.mutationObserver = null;
2141
2063
  this.isInit = true;
2142
2064
  this.boundUpdateChanged = this.updateChanged.bind(this);
2143
2065
  }
@@ -2183,7 +2105,8 @@
2183
2105
  return;
2184
2106
  }
2185
2107
  const rect = el.getBoundingClientRect();
2186
- const style = typeof window !== "undefined" && typeof window.getComputedStyle === "function"
2108
+ const style = typeof window !== "undefined" &&
2109
+ typeof window.getComputedStyle === "function"
2187
2110
  ? window.getComputedStyle(el)
2188
2111
  : null;
2189
2112
  const metrics = {
@@ -2315,36 +2238,10 @@
2315
2238
  * @param options - Configuration options (panel sizing, flags, texts, etc.).
2316
2239
  * @param modelManager - Model manager that supplies the adapter and recycler view.
2317
2240
  */
2318
- constructor(select = null, options = null, modelManager = null) {
2241
+ constructor(select, options, modelManager) {
2319
2242
  super();
2320
- /** Active configuration for the popup behavior and text labels */
2321
- this.options = null;
2322
2243
  /** Indicates whether the popup DOM has been attached to the document body at least once */
2323
2244
  this.isCreated = false;
2324
- /** Mixed adapter handling items/models and visibility stats */
2325
- this.optionAdapter = null;
2326
- /** Root popup container (the floating panel) */
2327
- this.node = null;
2328
- /** Effector service used to measure/animate the popup */
2329
- this.effSvc = null;
2330
- /** Resize observer to react to parent panel size changes */
2331
- this.resizeObser = null;
2332
- /** Binder map for parent elements (anchors to compute placement from) */
2333
- this.parent = null;
2334
- /** Header control exposing "Select All / Deselect All" actions */
2335
- this.optionHandle = null;
2336
- /** "Empty / Not found" feedback component */
2337
- this.emptyState = null;
2338
- /** Loading indicator component */
2339
- this.loadingState = null;
2340
- /** Virtualized recycler view for performant lists */
2341
- this.recyclerView = null;
2342
- /** Container that holds the list of options */
2343
- this.optionsContainer = null;
2344
- /** Scroll handler used by infinite scroll */
2345
- this.scrollListener = null;
2346
- /** Handle to defer hiding the loading indicator */
2347
- this.hideLoadHandle = null;
2348
2245
  /** Default virtual scroll configuration (tuned for typical option heights) */
2349
2246
  this.virtualScrollConfig = {
2350
2247
  /** Estimated item height in pixels (improves initial layout calculation) */
@@ -2352,7 +2249,7 @@
2352
2249
  /** Number of extra items to render above/below the viewport */
2353
2250
  overscan: 8,
2354
2251
  /** Whether the list contains items with dynamic (non-uniform) heights */
2355
- dynamicHeights: true
2252
+ dynamicHeights: true,
2356
2253
  };
2357
2254
  this.modelManager = modelManager;
2358
2255
  if (select && options) {
@@ -2399,7 +2296,8 @@
2399
2296
  },
2400
2297
  }, null);
2401
2298
  this.node = nodeMounted.view;
2402
- this.optionsContainer = nodeMounted.tags.OptionsContainer;
2299
+ this.optionsContainer = nodeMounted.tags
2300
+ .OptionsContainer;
2403
2301
  this.parent = Libs.getBinderMap(select);
2404
2302
  this.options = options;
2405
2303
  this.init();
@@ -2408,7 +2306,7 @@
2408
2306
  scrollEl: this.node,
2409
2307
  estimateItemHeight: this.virtualScrollConfig.estimateItemHeight,
2410
2308
  overscan: this.virtualScrollConfig.overscan,
2411
- dynamicHeights: this.virtualScrollConfig.dynamicHeights
2309
+ dynamicHeights: this.virtualScrollConfig.dynamicHeights,
2412
2310
  }
2413
2311
  : {};
2414
2312
  // Load ModelManager resources into the list container
@@ -2435,7 +2333,11 @@
2435
2333
  * - Triggers a resize to accommodate layout changes
2436
2334
  */
2437
2335
  async showLoading() {
2438
- if (!this.options || !this.loadingState || !this.optionHandle || !this.optionAdapter || !this.modelManager)
2336
+ if (!this.options ||
2337
+ !this.loadingState ||
2338
+ !this.optionHandle ||
2339
+ !this.optionAdapter ||
2340
+ !this.modelManager)
2439
2341
  return;
2440
2342
  if (this.hideLoadHandle)
2441
2343
  clearTimeout(this.hideLoadHandle);
@@ -2453,7 +2355,10 @@
2453
2355
  * Debounce: Uses `animationtime` as a short delay before hiding the loading indicator.
2454
2356
  */
2455
2357
  async hideLoading() {
2456
- if (!this.options || !this.loadingState || !this.optionAdapter || !this.modelManager)
2358
+ if (!this.options ||
2359
+ !this.loadingState ||
2360
+ !this.optionAdapter ||
2361
+ !this.modelManager)
2457
2362
  return;
2458
2363
  if (this.hideLoadHandle)
2459
2364
  clearTimeout(this.hideLoadHandle);
@@ -2494,7 +2399,10 @@
2494
2399
  * @param stats - Optionally provide precomputed visibility stats.
2495
2400
  */
2496
2401
  updateEmptyState(stats) {
2497
- if (!this.optionAdapter || !this.emptyState || !this.optionHandle || !this.optionsContainer)
2402
+ if (!this.optionAdapter ||
2403
+ !this.emptyState ||
2404
+ !this.optionHandle ||
2405
+ !this.optionsContainer)
2498
2406
  return;
2499
2407
  const s = stats ?? this.optionAdapter.getVisibilityStats();
2500
2408
  if (s.isEmpty) {
@@ -2578,8 +2486,12 @@
2578
2486
  * @param callback - Optional callback invoked when the opening animation completes.
2579
2487
  * @param isShowEmptyState - If true, applies the empty/not-found state before animation.
2580
2488
  */
2581
- open(callback = null, isShowEmptyState) {
2582
- if (!this.node || !this.options || !this.optionHandle || !this.parent || !this.effSvc)
2489
+ open(callback, isShowEmptyState) {
2490
+ if (!this.node ||
2491
+ !this.options ||
2492
+ !this.optionHandle ||
2493
+ !this.parent ||
2494
+ !this.effSvc)
2583
2495
  return;
2584
2496
  // Ensure one-time initialization
2585
2497
  this.load();
@@ -2628,8 +2540,11 @@
2628
2540
  *
2629
2541
  * @param callback - Optional callback invoked when the closing animation completes.
2630
2542
  */
2631
- close(callback = null) {
2632
- if (!this.isCreated || !this.options || !this.resizeObser || !this.effSvc)
2543
+ close(callback) {
2544
+ if (!this.isCreated ||
2545
+ !this.options ||
2546
+ !this.resizeObser ||
2547
+ !this.effSvc)
2633
2548
  return;
2634
2549
  const rv = this.recyclerView;
2635
2550
  rv?.suspend?.();
@@ -2788,7 +2703,9 @@
2788
2703
  let maxHeight = configMaxHeight;
2789
2704
  let realHeight = Math.min(contentHeight, maxHeight);
2790
2705
  const heightOri = spaceBelow - safeMargin;
2791
- if (realHeight >= configMinHeight ? heightOri >= configMinHeight : heightOri >= realHeight) {
2706
+ if (realHeight >= configMinHeight
2707
+ ? heightOri >= configMinHeight
2708
+ : heightOri >= realHeight) {
2792
2709
  position = "bottom";
2793
2710
  maxHeight = Math.min(spaceBelow - safeMargin, configMaxHeight);
2794
2711
  }
@@ -2888,78 +2805,8 @@
2888
2805
  *
2889
2806
  * @param options - Configuration such as placeholder, accessibility IDs, and flags.
2890
2807
  */
2891
- constructor(options = null) {
2808
+ constructor(options) {
2892
2809
  super();
2893
- /**
2894
- * The mount result returned by {@link Libs.mountNode}.
2895
- *
2896
- * Provides typed access to created DOM tags (e.g., `SearchInput`) and the root view.
2897
- * `null` before initialization and after destruction.
2898
- *
2899
- * @internal
2900
- */
2901
- this.nodeMounted = null;
2902
- /**
2903
- * Root container node of this component.
2904
- *
2905
- * Created during {@link initialize} and removed during {@link destroy}.
2906
- * Visibility is controlled by adding/removing the `hide` class.
2907
- */
2908
- this.node = null;
2909
- /**
2910
- * The `<input type="search">` element used to capture user queries.
2911
- *
2912
- * Cached for imperative operations (focus, placeholder updates, ARIA updates).
2913
- * `null` before initialization and after destruction.
2914
- *
2915
- * @internal
2916
- */
2917
- this.SearchInput = null;
2918
- /**
2919
- * External "search changed" hook.
2920
- *
2921
- * Invoked when the user edits text (via the `input` event) and the edit is not
2922
- * part of a handled control-key sequence (e.g., ArrowUp/Down/Tab/Enter/Escape).
2923
- *
2924
- * Ownership:
2925
- * - Implementations typically filter adapter/model state and refresh the list.
2926
- */
2927
- this.onSearch = null;
2928
- /**
2929
- * Options snapshot used for behavior toggles and attributes.
2930
- *
2931
- * Key fields typically consumed here:
2932
- * - `placeholder`: initial placeholder string
2933
- * - `searchable`: toggles readOnly + focus behavior on {@link show}
2934
- * - `SEID_LIST`: used as `aria-controls` value to bind to listbox container
2935
- *
2936
- * Cleared during {@link destroy}.
2937
- *
2938
- * @internal
2939
- */
2940
- this.options = null;
2941
- /**
2942
- * External navigation hook for list traversal.
2943
- *
2944
- * Called with:
2945
- * - `+1` for forward (ArrowDown / Tab)
2946
- * - `-1` for backward (ArrowUp)
2947
- *
2948
- * Typical consumers update highlight/active option in Adapter/RecyclerView.
2949
- */
2950
- this.onNavigate = null;
2951
- /**
2952
- * External "commit" hook (Enter key).
2953
- *
2954
- * Typical consumers confirm selection of the highlighted option or submit the current state.
2955
- */
2956
- this.onEnter = null;
2957
- /**
2958
- * External "cancel" hook (Escape key).
2959
- *
2960
- * Typical consumers close the popup, clear highlight, or reset interaction mode.
2961
- */
2962
- this.onEsc = null;
2963
2810
  this.options = options;
2964
2811
  if (options)
2965
2812
  this.initialize(options);
@@ -3225,17 +3072,7 @@
3225
3072
  *
3226
3073
  * @param query - CSS selector or element to control. When `null`, instance starts unbound.
3227
3074
  */
3228
- constructor(query = null) {
3229
- /**
3230
- * Timeout used to finalize expand/collapse/swipe animations.
3231
- * Cleared by {@link cancel}.
3232
- */
3233
- this.timeOut = null;
3234
- /**
3235
- * Timeout used to clear transitions after resize in non-animated scenarios.
3236
- * Cleared by {@link cancel}.
3237
- */
3238
- this.resizeTimeout = null;
3075
+ constructor(query) {
3239
3076
  /**
3240
3077
  * Internal animation flag set while a timed animation is in-flight.
3241
3078
  *
@@ -3419,7 +3256,9 @@
3419
3256
  const { duration = 200, onComplete } = config;
3420
3257
  const currentHeight = this.element.offsetHeight;
3421
3258
  const currentTop = this.element.offsetTop;
3422
- const position = this.element.classList.contains("position-top") ? "top" : "bottom";
3259
+ const position = this.element.classList.contains("position-top")
3260
+ ? "top"
3261
+ : "bottom";
3423
3262
  const isScrollable = this.element.scrollHeight - this.element.offsetHeight > 0;
3424
3263
  const finalTop = position === "top" ? currentTop + currentHeight : currentTop;
3425
3264
  requestAnimationFrame(() => {
@@ -3557,7 +3396,9 @@
3557
3396
  return this;
3558
3397
  this.cancel();
3559
3398
  const { duration = 200, width, left, top, maxHeight, realHeight, position = "bottom", animate = true, onComplete, } = config;
3560
- const currentPosition = this.element.classList.contains("position-top") ? "top" : "bottom";
3399
+ const currentPosition = this.element.classList.contains("position-top")
3400
+ ? "top"
3401
+ : "bottom";
3561
3402
  const isPositionChanged = currentPosition !== position;
3562
3403
  const isScrollable = this.element.scrollHeight > maxHeight;
3563
3404
  this.element.classList.toggle("position-top", position === "top");
@@ -3673,25 +3514,11 @@
3673
3514
  * - Calls {@link Lifecycle.init} immediately (`NEW → INITIALIZED`).
3674
3515
  *
3675
3516
  * @param {TOptions} options - Configuration options for the model.
3676
- * @param {TTarget | null} [targetElement=null] - Optional DOM element to bind.
3677
- * @param {TView | null} [view=null] - Optional view responsible for rendering this model.
3517
+ * @param {TTarget} [targetElement=null] - Optional DOM element to bind.
3518
+ * @param {TView} [view=null] - Optional view responsible for rendering this model.
3678
3519
  */
3679
- constructor(options, targetElement = null, view = null) {
3520
+ constructor(options, targetElement, view) {
3680
3521
  super();
3681
- /**
3682
- * The currently bound target DOM element.
3683
- *
3684
- * This element typically represents the source-of-truth node in the host DOM (e.g., a native `<option>`).
3685
- * May be replaced via {@link updateTarget} during reconciliation.
3686
- */
3687
- this.targetElement = null;
3688
- /**
3689
- * View instance responsible for rendering this model.
3690
- *
3691
- * Ownership: this model will destroy the view on {@link destroy}.
3692
- * The view may be attached/assigned by external orchestrators (Adapter/RecyclerView) after construction.
3693
- */
3694
- this.view = null;
3695
3522
  /**
3696
3523
  * Position index used by list infrastructure for ordering/tracking.
3697
3524
  * Semantics are library-specific (e.g., top-level index or adapter position).
@@ -3723,7 +3550,7 @@
3723
3550
  * - Assigns {@link targetElement}.
3724
3551
  * - Calls {@link Lifecycle.update} (guarded by lifecycle state).
3725
3552
  *
3726
- * @param {TTarget | null} targetElement - The new DOM element to associate with this model.
3553
+ * @param {TTarget} targetElement - The new DOM element to associate with this model.
3727
3554
  * @returns {void}
3728
3555
  */
3729
3556
  updateTarget(targetElement) {
@@ -3923,7 +3750,7 @@
3923
3750
  if (this.is(LifecycleState.DESTROYED)) {
3924
3751
  return;
3925
3752
  }
3926
- this.items.forEach(item => {
3753
+ this.items.forEach((item) => {
3927
3754
  item.destroy();
3928
3755
  });
3929
3756
  this.items = [];
@@ -4047,10 +3874,10 @@
4047
3874
  * Creates an option model.
4048
3875
  *
4049
3876
  * @param {SelectiveOptions} options - Shared configuration for models/views.
4050
- * @param {HTMLOptionElement | null} [targetElement=null] - Backing `<option>` element.
4051
- * @param {OptionView | null} [view=null] - Optional view used to render this model.
3877
+ * @param {HTMLOptionElement} [targetElement=null] - Backing `<option>` element.
3878
+ * @param {OptionView} [view=null] - Optional view used to render this model.
4052
3879
  */
4053
- constructor(options, targetElement = null, view = null) {
3880
+ constructor(options, targetElement, view) {
4054
3881
  super(options, targetElement, view);
4055
3882
  /**
4056
3883
  * External selection subscribers (emitted by the {@link selected} setter).
@@ -4074,11 +3901,6 @@
4074
3901
  this._visible = true;
4075
3902
  /** Highlight flag used for keyboard navigation / hover. */
4076
3903
  this._highlighted = false;
4077
- /**
4078
- * Parent group model (if this option belongs to a group).
4079
- * Assigned by grouping logic (e.g., GroupModel/MixedAdapter).
4080
- */
4081
- this.group = null;
4082
3904
  }
4083
3905
  /**
4084
3906
  * Initializes the model and precomputes the search key.
@@ -4207,6 +4029,32 @@
4207
4029
  }
4208
4030
  iEvents.callEvent([this, value], ...this.privOnInternalSelected);
4209
4031
  }
4032
+ /**
4033
+ * Resolved display mask for this option.
4034
+ *
4035
+ * The mask is the primary render label used by the UI layer and supports
4036
+ * optional inline tag translation / rich HTML rendering.
4037
+ *
4038
+ * Source priority:
4039
+ * 1. `data-mask` (`dataset.mask`)
4040
+ * 2. Native `<option>` text content (`targetElement.text`)
4041
+ *
4042
+ * Processing pipeline:
4043
+ * - Raw content is first passed through {@link Libs.tagTranslate}.
4044
+ * - When `options.allowHtml === true`, translated HTML is preserved.
4045
+ * - Otherwise, all markup is stripped via {@link Libs.stripHtml}.
4046
+ *
4047
+ * Unlike {@link text}, this getter prioritizes the custom dataset mask,
4048
+ * making it suitable for display overrides without mutating the native
4049
+ * `<option>` label.
4050
+ *
4051
+ * @returns {string} Render-ready option label.
4052
+ */
4053
+ get mask() {
4054
+ const raw = this.dataset?.mask ?? this.targetElement?.text ?? "";
4055
+ const translated = Libs.tagTranslate(raw);
4056
+ return this.options.allowHtml ? translated : Libs.stripHtml(translated);
4057
+ }
4210
4058
  /**
4211
4059
  * Display label for rendering (with tag translation and HTML policy).
4212
4060
  *
@@ -4220,7 +4068,7 @@
4220
4068
  * @returns {string}
4221
4069
  */
4222
4070
  get text() {
4223
- const raw = this.dataset?.mask ?? this.targetElement?.text ?? "";
4071
+ const raw = this.targetElement?.text ?? this.dataset?.mask ?? "";
4224
4072
  const translated = Libs.tagTranslate(raw);
4225
4073
  return this.options.allowHtml ? translated : Libs.stripHtml(translated);
4226
4074
  }
@@ -4233,7 +4081,9 @@
4233
4081
  * @returns {string}
4234
4082
  */
4235
4083
  get textContent() {
4236
- return this.options.allowHtml ? Libs.stripHtml(this.text).trim() : this.text.trim();
4084
+ return this.options.allowHtml
4085
+ ? Libs.stripHtml(this.text).trim()
4086
+ : this.text.trim();
4237
4087
  }
4238
4088
  /**
4239
4089
  * Dataset object of the backing `<option>` element.
@@ -4414,8 +4264,6 @@
4414
4264
  constructor(options) {
4415
4265
  super();
4416
4266
  this.privModelList = [];
4417
- this.privAdapterHandle = null;
4418
- this.privRecyclerViewHandle = null;
4419
4267
  this.options = null;
4420
4268
  this.oldPosition = 0;
4421
4269
  this.options = options;
@@ -4467,7 +4315,9 @@
4467
4315
  else if (data.tagName === "OPTION") {
4468
4316
  const optionModel = new OptionModel(this.options, data);
4469
4317
  const parentGroup = data["__parentGroup"];
4470
- if (parentGroup && currentGroup && parentGroup === currentGroup.targetElement) {
4318
+ if (parentGroup &&
4319
+ currentGroup &&
4320
+ parentGroup === currentGroup.targetElement) {
4471
4321
  currentGroup.addItem(optionModel);
4472
4322
  optionModel.group = currentGroup;
4473
4323
  }
@@ -4892,44 +4742,10 @@
4892
4742
  /**
4893
4743
  * Creates an AccessoryBox and optionally initializes it with configuration.
4894
4744
  *
4895
- * @param {SelectiveOptions | null} [options=null] - Configuration controlling placement/visibility and texts.
4745
+ * @param {SelectiveOptions} [options=null] - Configuration controlling placement/visibility and texts.
4896
4746
  */
4897
- constructor(options = null) {
4747
+ constructor(options) {
4898
4748
  super();
4899
- /**
4900
- * Mounted structure returned by the node mounting helper.
4901
- * Contains the root element (`view`) and any tag handles (if present).
4902
- */
4903
- this.nodeMounted = null;
4904
- /**
4905
- * Root DOM element of the accessory box (hidden by default).
4906
- * Created during {@link init} and removed during {@link destroy}.
4907
- */
4908
- this.node = null;
4909
- /**
4910
- * Component configuration (texts, behavior, placement).
4911
- * This component reads:
4912
- * - `accessoryStyle` ("top" or default bottom)
4913
- * - `accessoryVisible` (enable/disable)
4914
- * - `multiple` (multi-select mode)
4915
- * - `textAccessoryDeselect` (a11y label prefix)
4916
- */
4917
- this.options = null;
4918
- /**
4919
- * The Select UI mask element used as the positioning reference.
4920
- * Provided by {@link setRoot}.
4921
- */
4922
- this.selectUIMask = null;
4923
- /**
4924
- * Parent container that hosts both the Select UI mask and the accessory box.
4925
- * Computed from `selectUIMask.parentElement`.
4926
- */
4927
- this.parentMask = null;
4928
- /**
4929
- * ModelManager used to run selection pipelines and coordinate state updates.
4930
- * This component does not own selection state; it delegates to the model layer.
4931
- */
4932
- this.modelManager = null;
4933
4749
  /**
4934
4750
  * Current selected option models rendered as chips.
4935
4751
  * This is a cached snapshot used for show/hide decisions and re-rendering.
@@ -5237,15 +5053,6 @@
5237
5053
  */
5238
5054
  constructor(selectElement, modelManager, selectBox) {
5239
5055
  super();
5240
- /**
5241
- * AJAX configuration; when `null`, {@link search} falls back to local filtering.
5242
- * @see {@link setAjax}
5243
- */
5244
- this.ajaxConfig = null;
5245
- /** Abort handle used to cancel an in-flight AJAX request when a newer request starts. */
5246
- this.abortController = null;
5247
- /** Optional popup handle used for showing/hiding loading UI during remote operations. */
5248
- this.popup = null;
5249
5056
  /**
5250
5057
  * SelectBox handle used by custom data builder functions that require Selective context.
5251
5058
  * NOTE: This is a reference; the controller does not own/destroy the SelectBox.
@@ -5309,7 +5116,11 @@
5309
5116
  */
5310
5117
  async loadByValues(values) {
5311
5118
  if (!this.ajaxConfig) {
5312
- return { success: false, items: [], message: "Ajax not configured" };
5119
+ return {
5120
+ success: false,
5121
+ items: [],
5122
+ message: "Ajax not configured",
5123
+ };
5313
5124
  }
5314
5125
  const valuesArray = Array.isArray(values) ? values : [values];
5315
5126
  if (valuesArray.length === 0)
@@ -5326,7 +5137,7 @@
5326
5137
  load_by_values: "1",
5327
5138
  ...(typeof cfg.data === "function"
5328
5139
  ? cfg.data.bind(this.selectBox.Selective.find(this.selectBox.container.targetElement))("", 0)
5329
- : cfg.data ?? {}),
5140
+ : (cfg.data ?? {})),
5330
5141
  };
5331
5142
  }
5332
5143
  let response;
@@ -5336,7 +5147,9 @@
5336
5147
  response = await fetch(cfg.url, {
5337
5148
  method: "POST",
5338
5149
  body: formData,
5339
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
5150
+ headers: {
5151
+ "Content-Type": "application/x-www-form-urlencoded",
5152
+ },
5340
5153
  });
5341
5154
  }
5342
5155
  else {
@@ -5373,7 +5186,7 @@
5373
5186
  * Configures AJAX settings used for remote searching and pagination.
5374
5187
  * Setting `null` disables AJAX mode and causes {@link search} to use local filtering.
5375
5188
  *
5376
- * @param {AjaxConfig | null} config - AJAX configuration (endpoint, method, data builders, keepSelected, ...).
5189
+ * @param {AjaxConfig} config - AJAX configuration (endpoint, method, data builders, keepSelected, ...).
5377
5190
  * @returns {void}
5378
5191
  */
5379
5192
  setAjax(config) {
@@ -5563,7 +5376,12 @@
5563
5376
  payload.selectedValue = selectedValues;
5564
5377
  }
5565
5378
  else {
5566
- payload = { search: keyword, page, selectedValue: selectedValues, ...(cfg.data ?? {}) };
5379
+ payload = {
5380
+ search: keyword,
5381
+ page,
5382
+ selectedValue: selectedValues,
5383
+ ...(cfg.data ?? {}),
5384
+ };
5567
5385
  }
5568
5386
  try {
5569
5387
  let response;
@@ -5573,13 +5391,17 @@
5573
5391
  response = await fetch(cfg.url, {
5574
5392
  method: "POST",
5575
5393
  body: formData,
5576
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
5394
+ headers: {
5395
+ "Content-Type": "application/x-www-form-urlencoded",
5396
+ },
5577
5397
  signal: this.abortController.signal,
5578
5398
  });
5579
5399
  }
5580
5400
  else {
5581
5401
  const params = new URLSearchParams(payload).toString();
5582
- response = await fetch(`${cfg.url}?${params}`, { signal: this.abortController.signal });
5402
+ response = await fetch(`${cfg.url}?${params}`, {
5403
+ signal: this.abortController.signal,
5404
+ });
5583
5405
  }
5584
5406
  const data = await response.json();
5585
5407
  const result = this.parseResponse(data);
@@ -5653,7 +5475,7 @@
5653
5475
  hasPagination = true;
5654
5476
  page = parseInt(data.page ?? 0, 10);
5655
5477
  totalPages = parseInt(data.totalPages ?? data.total_page ?? 1, 10);
5656
- hasMore = data.hasMore ?? (page < totalPages - 1);
5478
+ hasMore = data.hasMore ?? page < totalPages - 1;
5657
5479
  }
5658
5480
  }
5659
5481
  else if (Array.isArray(data)) {
@@ -5664,23 +5486,39 @@
5664
5486
  if (data.pagination) {
5665
5487
  hasPagination = true;
5666
5488
  page = parseInt(data.pagination.page ?? 0, 10);
5667
- totalPages = parseInt(data.pagination.totalPages ?? data.pagination.total_page ?? 1, 10);
5668
- hasMore = data.pagination.hasMore ?? (page < totalPages - 1);
5489
+ totalPages = parseInt(data.pagination.totalPages ??
5490
+ data.pagination.total_page ??
5491
+ 1, 10);
5492
+ hasMore = data.pagination.hasMore ?? page < totalPages - 1;
5669
5493
  }
5670
5494
  }
5671
5495
  const normalized = items.map((item) => {
5672
- if (item instanceof HTMLOptionElement || item instanceof HTMLOptGroupElement)
5496
+ if (item instanceof HTMLOptionElement ||
5497
+ item instanceof HTMLOptGroupElement)
5673
5498
  return item;
5674
- if (item.type === "optgroup" || item.isGroup || item.group || item.label) {
5499
+ if (item.type === "optgroup" ||
5500
+ item.isGroup ||
5501
+ item.group ||
5502
+ item.label) {
5675
5503
  const label = item.label ?? item.name ?? item.title ?? "";
5676
5504
  const dataObj = item.data ?? {};
5677
5505
  const opts = (item.options ?? item.items ?? []).map((opt) => ({
5678
5506
  value: opt.value ?? opt.id ?? opt.key ?? "",
5679
- text: opt.text ?? opt.label ?? opt.name ?? opt.title ?? "",
5507
+ text: opt.text ??
5508
+ opt.label ??
5509
+ opt.name ??
5510
+ opt.title ??
5511
+ "",
5680
5512
  selected: opt.selected ?? false,
5681
- data: opt.data ?? (opt.imgsrc ? { imgsrc: opt.imgsrc } : {}),
5513
+ data: opt.data ??
5514
+ (opt.imgsrc ? { imgsrc: opt.imgsrc } : {}),
5682
5515
  }));
5683
- return { type: "optgroup", label, data: dataObj, options: opts };
5516
+ return {
5517
+ type: "optgroup",
5518
+ label,
5519
+ data: dataObj,
5520
+ options: opts,
5521
+ };
5684
5522
  }
5685
5523
  const dataObj = item.data ?? {};
5686
5524
  if (item?.imgsrc)
@@ -5722,9 +5560,12 @@
5722
5560
  select.innerHTML = "";
5723
5561
  items.forEach((item) => {
5724
5562
  // Skip empty item (defensive guard)
5725
- if ((item["type"] === "option" || !item["type"]) && item["value"] === "" && item["text"] === "")
5563
+ if ((item["type"] === "option" || !item["type"]) &&
5564
+ item["value"] === "" &&
5565
+ item["text"] === "")
5726
5566
  return;
5727
- if (item instanceof HTMLOptionElement || item instanceof HTMLOptGroupElement) {
5567
+ if (item instanceof HTMLOptionElement ||
5568
+ item instanceof HTMLOptGroupElement) {
5728
5569
  select.appendChild(item);
5729
5570
  return;
5730
5571
  }
@@ -5746,7 +5587,8 @@
5746
5587
  option.dataset[key] = String(opt.data[key]);
5747
5588
  });
5748
5589
  }
5749
- if (opt.selected || (keepSelected && oldSelected.includes(option.value))) {
5590
+ if (opt.selected ||
5591
+ (keepSelected && oldSelected.includes(option.value))) {
5750
5592
  option.selected = true;
5751
5593
  }
5752
5594
  optgroup.appendChild(option);
@@ -5763,7 +5605,8 @@
5763
5605
  option.dataset[key] = String(item.data[key]);
5764
5606
  });
5765
5607
  }
5766
- if (item.selected || (keepSelected && oldSelected.includes(option.value))) {
5608
+ if (item.selected ||
5609
+ (keepSelected && oldSelected.includes(option.value))) {
5767
5610
  option.selected = true;
5768
5611
  }
5769
5612
  select.appendChild(option);
@@ -5839,16 +5682,6 @@
5839
5682
  * @param {HTMLSelectElement} select - The `<select>` element to observe for mutations.
5840
5683
  */
5841
5684
  constructor(select) {
5842
- /**
5843
- * Debounce timer handle for batching rapid mutations.
5844
- *
5845
- * - Cleared and reset on each mutation event.
5846
- * - Invokes {@link handleChange} after {@link _DEBOUNCE_DELAY} milliseconds of inactivity.
5847
- * - Nulled during {@link disconnect}.
5848
- *
5849
- * @private
5850
- */
5851
- this.debounceTimer = null;
5852
5685
  /**
5853
5686
  * Debounce delay in milliseconds.
5854
5687
  *
@@ -5989,16 +5822,12 @@
5989
5822
  * @param element - The element whose `data-*` attributes will be observed.
5990
5823
  */
5991
5824
  constructor(element) {
5992
- /**
5993
- * Debounce timer handle for coalescing rapid attribute mutations.
5994
- * Cleared/replaced whenever a new relevant mutation arrives within the debounce window.
5995
- */
5996
- this.debounceTimer = null;
5997
5825
  this.element = element;
5998
5826
  this.observer = new MutationObserver((mutations) => {
5999
5827
  let datasetChanged = false;
6000
5828
  for (const mutation of mutations) {
6001
- if (mutation.type === "attributes" && mutation.attributeName?.startsWith("data-")) {
5829
+ if (mutation.type === "attributes" &&
5830
+ mutation.attributeName?.startsWith("data-")) {
6002
5831
  datasetChanged = true;
6003
5832
  break;
6004
5833
  }
@@ -6147,6 +5976,16 @@
6147
5976
  * This flag is intentionally generic and is coordinated by higher-level components.
6148
5977
  */
6149
5978
  this.isSkipEvent = false;
5979
+ /**
5980
+ * Tracks all scheduler keys registered by this adapter instance via
5981
+ * {@link onPropChanging} and {@link onPropChanged}.
5982
+ *
5983
+ * Used during {@link destroy} to clean up all associated pipelines
5984
+ * from the global {@link Libs.callbackScheduler}.
5985
+ *
5986
+ * Keys are deduplicated automatically by Set semantics.
5987
+ */
5988
+ this.callbackSchedulerList = new Set();
6150
5989
  this.items = items;
6151
5990
  this.init();
6152
5991
  }
@@ -6188,7 +6027,9 @@
6188
6027
  * @see {@link changingProp}
6189
6028
  */
6190
6029
  onPropChanging(propName, callback) {
6191
- Libs.callbackScheduler.on(`${propName}ing_${this.adapterKey}`, callback, { debounce: 0 });
6030
+ const key = `${propName}ing_${this.adapterKey}`;
6031
+ Libs.callbackScheduler.on(key, callback, { debounce: 0 });
6032
+ this.callbackSchedulerList.add(key);
6192
6033
  }
6193
6034
  /**
6194
6035
  * Registers a **post-change** callback for a property pipeline.
@@ -6204,7 +6045,11 @@
6204
6045
  * @see {@link changeProp}
6205
6046
  */
6206
6047
  onPropChanged(propName, callback) {
6207
- Libs.callbackScheduler.on(`${propName}_${this.adapterKey}`, callback, { debounce: 0 });
6048
+ const key = `${propName}_${this.adapterKey}`;
6049
+ Libs.callbackScheduler.on(key, callback, {
6050
+ debounce: 0,
6051
+ });
6052
+ this.callbackSchedulerList.add(key);
6208
6053
  }
6209
6054
  /**
6210
6055
  * Triggers the **post-change** pipeline for a given property.
@@ -6239,7 +6084,7 @@
6239
6084
  *
6240
6085
  * @param {HTMLElement} parent - Container element that will host the viewer.
6241
6086
  * @param {TItem} item - The model for which the viewer is created.
6242
- * @returns {TViewer | null} The created viewer instance; `null` by default.
6087
+ * @returns {TViewer} The created viewer instance; `null` by default.
6243
6088
  */
6244
6089
  viewHolder(parent, item) {
6245
6090
  return null;
@@ -6345,11 +6190,16 @@
6345
6190
  if (this.is(LifecycleState.DESTROYED)) {
6346
6191
  return;
6347
6192
  }
6193
+ this.callbackSchedulerList.forEach((key) => {
6194
+ Libs.callbackScheduler.off(key);
6195
+ });
6196
+ this.callbackSchedulerList.clear();
6348
6197
  this.recyclerView = null;
6349
- this.items.forEach(item => {
6198
+ this.items.forEach((item) => {
6350
6199
  item?.destroy?.();
6351
6200
  });
6352
6201
  this.items = [];
6202
+ super.destroy();
6353
6203
  }
6354
6204
  }
6355
6205
 
@@ -6401,20 +6251,6 @@
6401
6251
  */
6402
6252
  constructor(parent) {
6403
6253
  super();
6404
- /**
6405
- * Host container element into which this view's root element is rendered/attached.
6406
- *
6407
- * This reference is captured at construction time and cleared on {@link destroy}.
6408
- */
6409
- this.parent = null;
6410
- /**
6411
- * Mounted view result containing:
6412
- * - `view`: the root element of this view
6413
- * - `tags`: a strongly-typed map of child elements for fast access
6414
- *
6415
- * This is expected to be assigned by subclasses (or a mount helper) before {@link getView} is called.
6416
- */
6417
- this.view = null;
6418
6254
  this.parent = parent;
6419
6255
  this.init();
6420
6256
  }
@@ -6515,29 +6351,6 @@
6515
6351
  */
6516
6352
  constructor(parent, options) {
6517
6353
  super(parent);
6518
- /**
6519
- * Strongly-typed reference to the mounted group view structure.
6520
- *
6521
- * Structure:
6522
- * - **view**: Root container element.
6523
- * - **tags**: Named references to header and items container.
6524
- *
6525
- * Lifecycle:
6526
- * - `null` until {@link mount} completes.
6527
- * - Cleared during {@link destroy}.
6528
- *
6529
- * @public
6530
- */
6531
- this.view = null;
6532
- /**
6533
- * Parsed configuration (bound from the `<select>` element via binder map).
6534
- *
6535
- * Provides feature flags (multiple/disabled/readonly/visible/virtualScroll/ajax/autoclose…),
6536
- * a11y ids (e.g. `SEID_LIST`, `SEID_HOLDER`) and user callbacks under `options.on`.
6537
- *
6538
- * @internal
6539
- */
6540
- this.options = null;
6541
6354
  this.options = options;
6542
6355
  }
6543
6356
  /**
@@ -6631,10 +6444,10 @@
6631
6444
  * - Safe to call multiple times with same value (idempotent).
6632
6445
  *
6633
6446
  * @public
6634
- * @param {string | null} [label=null] - New label to display; `null` preserves current label.
6447
+ * @param {string} [label=null] - New label to display; `null` preserves current label.
6635
6448
  * @returns {void}
6636
6449
  */
6637
- updateLabel(label = null) {
6450
+ updateLabel(label) {
6638
6451
  if (!this.view)
6639
6452
  return;
6640
6453
  const headerEl = this.view.tags.GroupHeader;
@@ -6681,7 +6494,7 @@
6681
6494
  if (!this.view)
6682
6495
  return;
6683
6496
  const items = this.view.tags.GroupItems;
6684
- const visibleItems = Array.from(items.children).filter(child => !child.classList.contains("hide"));
6497
+ const visibleItems = Array.from(items.children).filter((child) => !child.classList.contains("hide"));
6685
6498
  this.view.view.classList.toggle("hide", visibleItems.length === 0);
6686
6499
  }
6687
6500
  /**
@@ -6795,57 +6608,6 @@
6795
6608
  */
6796
6609
  constructor(parent, options) {
6797
6610
  super(parent);
6798
- /**
6799
- * Strongly-typed reference to the mounted option view structure.
6800
- *
6801
- * Structure:
6802
- * - **view**: Root container element.
6803
- * - **tags**: Named references to input, image (conditional), label, label content.
6804
- *
6805
- * Lifecycle:
6806
- * - `null` until {@link mount} completes.
6807
- * - Cleared during {@link destroy}.
6808
- *
6809
- * @public
6810
- */
6811
- this.view = null;
6812
- /**
6813
- * Parsed configuration (bound from the `<select>` element via binder map).
6814
- *
6815
- * Provides feature flags (multiple/disabled/readonly/visible/virtualScroll/ajax/autoclose…),
6816
- * a11y ids (e.g. `SEID_LIST`, `SEID_HOLDER`) and user callbacks under `options.on`.
6817
- *
6818
- * @internal
6819
- */
6820
- this.options = null;
6821
- /**
6822
- * Internal configuration object (Proxy target).
6823
- *
6824
- * Lifecycle:
6825
- * - Initialized during {@link initialize} with default values.
6826
- * - Mutated via {@link configProxy} Proxy trap.
6827
- *
6828
- * Notes:
6829
- * - **Should not be mutated directly**; use {@link configProxy} or typed setters.
6830
- * - Contains default values for layout, image, and alignment.
6831
- *
6832
- * @private
6833
- */
6834
- this.config = null;
6835
- /**
6836
- * Reactive Proxy wrapper around {@link config}.
6837
- *
6838
- * Behavior:
6839
- * - Intercepts property assignments via `set` trap.
6840
- * - Triggers {@link applyPartialChange} for diffed values when {@link isRendered} is `true`.
6841
- * - Prevents redundant DOM updates when value hasn't changed.
6842
- *
6843
- * Usage:
6844
- * - Accessed via {@link optionConfig} getter or typed setters ({@link isMultiple}, {@link hasImage}).
6845
- *
6846
- * @private
6847
- */
6848
- this.configProxy = null;
6849
6611
  /**
6850
6612
  * Flag indicating whether the initial render has completed.
6851
6613
  *
@@ -7010,24 +6772,30 @@
7010
6772
  * - Each changed property triggers {@link applyPartialChange} individually.
7011
6773
  *
7012
6774
  * @public
7013
- * @param {OptionConfigPatch | null} config - Partial configuration patch; `null` is no-op.
6775
+ * @param {OptionConfigPatch} config - Partial configuration patch; `null` is no-op.
7014
6776
  * @returns {void}
7015
6777
  */
7016
6778
  set optionConfig(config) {
7017
6779
  if (!config || !this.configProxy || !this.config)
7018
6780
  return;
7019
6781
  const changes = {};
7020
- if (config.imageWidth !== undefined && config.imageWidth !== this.config.imageWidth)
6782
+ if (config.imageWidth !== undefined &&
6783
+ config.imageWidth !== this.config.imageWidth)
7021
6784
  changes.imageWidth = config.imageWidth;
7022
- if (config.imageHeight !== undefined && config.imageHeight !== this.config.imageHeight)
6785
+ if (config.imageHeight !== undefined &&
6786
+ config.imageHeight !== this.config.imageHeight)
7023
6787
  changes.imageHeight = config.imageHeight;
7024
- if (config.imageBorderRadius !== undefined && config.imageBorderRadius !== this.config.imageBorderRadius)
6788
+ if (config.imageBorderRadius !== undefined &&
6789
+ config.imageBorderRadius !== this.config.imageBorderRadius)
7025
6790
  changes.imageBorderRadius = config.imageBorderRadius;
7026
- if (config.imagePosition !== undefined && config.imagePosition !== this.config.imagePosition)
6791
+ if (config.imagePosition !== undefined &&
6792
+ config.imagePosition !== this.config.imagePosition)
7027
6793
  changes.imagePosition = config.imagePosition;
7028
- if (config.labelValign !== undefined && config.labelValign !== this.config.labelValign)
6794
+ if (config.labelValign !== undefined &&
6795
+ config.labelValign !== this.config.labelValign)
7029
6796
  changes.labelValign = config.labelValign;
7030
- if (config.labelHalign !== undefined && config.labelHalign !== this.config.labelHalign)
6797
+ if (config.labelHalign !== undefined &&
6798
+ config.labelHalign !== this.config.labelHalign)
7031
6799
  changes.labelHalign = config.labelHalign;
7032
6800
  if (Object.keys(changes).length > 0) {
7033
6801
  Object.assign(this.configProxy, changes);
@@ -7201,9 +6969,11 @@
7201
6969
  case "imageBorderRadius": {
7202
6970
  const img = v.tags?.OptionImage;
7203
6971
  if (img) {
7204
- const styleProp = prop === "imageWidth" ? "width" :
7205
- prop === "imageHeight" ? "height" :
7206
- "borderRadius";
6972
+ const styleProp = prop === "imageWidth"
6973
+ ? "width"
6974
+ : prop === "imageHeight"
6975
+ ? "height"
6976
+ : "borderRadius";
7207
6977
  img.style[styleProp] = String(newValue);
7208
6978
  }
7209
6979
  break;
@@ -7324,15 +7094,6 @@
7324
7094
  super(items);
7325
7095
  /** Whether the adapter operates in multi-selection mode. */
7326
7096
  this.isMultiple = false;
7327
- /**
7328
- * Parsed configuration (bound from the `<select>` element via binder map).
7329
- *
7330
- * Provides feature flags (multiple/disabled/readonly/visible/virtualScroll/ajax/autoclose…),
7331
- * a11y ids (e.g. `SEID_LIST`, `SEID_HOLDER`) and user callbacks under `options.on`.
7332
- *
7333
- * @internal
7334
- */
7335
- this.options = null;
7336
7097
  /**
7337
7098
  * Subscribers for aggregated visibility statistics.
7338
7099
  * Fired via a debounced scheduler to avoid repeated recomputation during batch updates.
@@ -7343,11 +7104,6 @@
7343
7104
  * `-1` indicates "no highlight".
7344
7105
  */
7345
7106
  this.currentHighlightIndex = -1;
7346
- /**
7347
- * Cached pointer to the selected option in single-select mode.
7348
- * Used to efficiently clear previous selection when selecting a new option.
7349
- */
7350
- this.selectedItemSingle = null;
7351
7107
  /** Top-level group models (if any). */
7352
7108
  this.groups = [];
7353
7109
  /**
@@ -7436,7 +7192,7 @@
7436
7192
  * - Performs one-time listener binding guarded by `item.isInit`.
7437
7193
  *
7438
7194
  * @param {MixedItem} item - {@link GroupModel} or {@link OptionModel}.
7439
- * @param {GroupView | OptionView | null} viewer - The view instance that will render the model.
7195
+ * @param {GroupView | OptionView} viewer - The view instance that will render the model.
7440
7196
  * @param {number} position - Position in the top-level mixed list.
7441
7197
  * @returns {void}
7442
7198
  * @override
@@ -7663,7 +7419,7 @@
7663
7419
  return;
7664
7420
  }
7665
7421
  Libs.callbackScheduler.clear(`sche_vis_${this.adapterKey}`);
7666
- this.groups.forEach(group => {
7422
+ this.groups.forEach((group) => {
7667
7423
  group.destroy();
7668
7424
  });
7669
7425
  this.visibilityChangedCallbacks = [];
@@ -7743,7 +7499,7 @@
7743
7499
  * @returns {void}
7744
7500
  */
7745
7501
  resetHighlight() {
7746
- this.setHighlight(0);
7502
+ this.setHighlight(0, false);
7747
7503
  }
7748
7504
  /**
7749
7505
  * Moves highlight among **visible** options and optionally scrolls the new target into view.
@@ -7786,7 +7542,8 @@
7786
7542
  * @returns {void}
7787
7543
  */
7788
7544
  selectHighlighted() {
7789
- if (this.currentHighlightIndex > -1 && this.flatOptions[this.currentHighlightIndex]) {
7545
+ if (this.currentHighlightIndex > -1 &&
7546
+ this.flatOptions[this.currentHighlightIndex]) {
7790
7547
  const item = this.flatOptions[this.currentHighlightIndex];
7791
7548
  if (item.visible) {
7792
7549
  const viewEl = item.view?.getView?.();
@@ -7823,7 +7580,8 @@
7823
7580
  else {
7824
7581
  index = 0;
7825
7582
  }
7826
- if (this.currentHighlightIndex > -1 && this.flatOptions[this.currentHighlightIndex]) {
7583
+ if (this.currentHighlightIndex > -1 &&
7584
+ this.flatOptions[this.currentHighlightIndex]) {
7827
7585
  this.flatOptions[this.currentHighlightIndex].highlighted = false;
7828
7586
  }
7829
7587
  for (let i = index; i < this.flatOptions.length; i++) {
@@ -7834,12 +7592,13 @@
7834
7592
  this.currentHighlightIndex = i;
7835
7593
  if (isScrollToView) {
7836
7594
  const el = item.view?.getView?.();
7837
- if (el) {
7838
- el.scrollIntoView({ block: 'center', behavior: 'smooth' });
7595
+ if (el?.isConnected) {
7596
+ el.scrollIntoView({ block: "center", behavior: "smooth" });
7839
7597
  }
7840
7598
  else {
7841
- // If virtualized, ensure the item is rendered before trying to scroll.
7842
- this.recyclerView?.ensureRendered?.(i, { scrollIntoView: true });
7599
+ this.recyclerView?.ensureRendered?.(i, {
7600
+ scrollIntoView: true,
7601
+ });
7843
7602
  }
7844
7603
  }
7845
7604
  this.onHighlightChange(i, item.view?.getView?.()?.id);
@@ -8068,9 +7827,9 @@
8068
7827
  *
8069
7828
  * Note: The virtualization scaffold is built when an adapter is set via {@link setAdapter}.
8070
7829
  *
8071
- * @param {HTMLDivElement | null} [viewElement=null] - Optional root container for the recycler view.
7830
+ * @param {HTMLDivElement} [viewElement=null] - Optional root container for the recycler view.
8072
7831
  */
8073
- constructor(viewElement = null) {
7832
+ constructor(viewElement) {
8074
7833
  super(viewElement);
8075
7834
  /**
8076
7835
  * Virtualization settings (materialized to `Required<VirtualOptions>`).
@@ -8106,15 +7865,18 @@
8106
7865
  this.start = 0;
8107
7866
  /** Current window end (inclusive). -1 means not initialized. */
8108
7867
  this.end = -1;
8109
- /** Pending animation frame ids for window and measurement. */
8110
- this.rafId = null;
8111
- this.measureRaf = null;
8112
7868
  /** Re-entrancy/suspension flags used to prevent feedback loops. */
8113
7869
  this.updating = false;
8114
7870
  this.suppressResize = false;
8115
7871
  this.lastRenderCount = 0;
8116
7872
  this.suspended = false;
8117
7873
  this.resumeResizeAfter = false;
7874
+ /**
7875
+ * When set, scrollToIndex() will be called after the next measureVisibleAndUpdate()
7876
+ * completes and Fenwick has been updated with real heights.
7877
+ * Set by ensureRendered() and cleared after the corrective scroll fires.
7878
+ */
7879
+ this.pendingScrollToIndex = null;
8118
7880
  /** Small cache for sticky header height (≈16ms TTL) to limit layout reads. */
8119
7881
  this.stickyCacheTick = 0;
8120
7882
  this.stickyCacheVal = 0;
@@ -8163,20 +7925,29 @@
8163
7925
  return;
8164
7926
  this.viewElement.replaceChildren();
8165
7927
  const nodeMounted = Libs.mountNode({
8166
- PadTop: { tag: { node: "div", classList: "seui-virtual-pad-top" } },
8167
- ItemsHost: { tag: { node: "div", classList: "seui-virtual-items" } },
8168
- PadBottom: { tag: { node: "div", classList: "seui-virtual-pad-bottom" } },
7928
+ PadTop: {
7929
+ tag: { node: "div", classList: "seui-virtual-pad-top" },
7930
+ },
7931
+ ItemsHost: {
7932
+ tag: { node: "div", classList: "seui-virtual-items" },
7933
+ },
7934
+ PadBottom: {
7935
+ tag: { node: "div", classList: "seui-virtual-pad-bottom" },
7936
+ },
8169
7937
  }, this.viewElement);
8170
7938
  this.PadTop = nodeMounted.PadTop;
8171
7939
  this.ItemsHost = nodeMounted.ItemsHost;
8172
7940
  this.PadBottom = nodeMounted.PadBottom;
8173
- this.scrollEl = this.opts.scrollEl
8174
- ?? this.viewElement.closest(".seui-popup")
8175
- ?? this.viewElement.parentElement;
7941
+ this.scrollEl =
7942
+ this.opts.scrollEl ??
7943
+ this.viewElement.closest(".seui-popup") ??
7944
+ this.viewElement.parentElement;
8176
7945
  if (!this.scrollEl)
8177
7946
  throw new Error("VirtualRecyclerView: scrollEl not found");
8178
7947
  this.boundOnScroll = this.onScroll.bind(this);
8179
- this.scrollEl.addEventListener("scroll", this.boundOnScroll, { passive: true });
7948
+ this.scrollEl.addEventListener("scroll", this.boundOnScroll, {
7949
+ passive: true,
7950
+ });
8180
7951
  this.refresh(false);
8181
7952
  this.attachResizeObserverOnce();
8182
7953
  adapter?.onVisibilityChanged?.(() => this.refreshItem());
@@ -8215,7 +7986,9 @@
8215
7986
  resume() {
8216
7987
  this.suspended = false;
8217
7988
  if (this.scrollEl && this.boundOnScroll) {
8218
- this.scrollEl.addEventListener("scroll", this.boundOnScroll, { passive: true });
7989
+ this.scrollEl.addEventListener("scroll", this.boundOnScroll, {
7990
+ passive: true,
7991
+ });
8219
7992
  }
8220
7993
  if (this.resumeResizeAfter) {
8221
7994
  this.attachResizeObserverOnce();
@@ -8270,9 +8043,19 @@
8270
8043
  * @returns {void}
8271
8044
  */
8272
8045
  ensureRendered(index, opt) {
8273
- this.mountRange(index, index);
8274
- if (opt?.scrollIntoView)
8275
- this.scrollToIndex(index);
8046
+ if (!opt?.scrollIntoView) {
8047
+ // No scroll requested — mount only (legacy path, used by probes).
8048
+ this.mountRange(index, index);
8049
+ return;
8050
+ }
8051
+ // Pass 1: instant — brings window to vicinity, triggers measure.
8052
+ // Must be instant so Pass 2 smooth scroll isn't interrupted mid-animation.
8053
+ this.scrollToIndex(index, "instant");
8054
+ // Pass 2: measureVisibleAndUpdate() will consume this and fire a corrective
8055
+ // smooth scroll after Fenwick has been updated with real heights.
8056
+ // rv.resume() is guaranteed to run before this callback (popup.open onComplete
8057
+ // calls rv.resume() first), so the window is already rendered when we arrive here.
8058
+ this.pendingScrollToIndex = index;
8276
8059
  }
8277
8060
  /**
8278
8061
  * Scrolls the scroll container to align the item at `index` into view.
@@ -8286,15 +8069,25 @@
8286
8069
  * @param {number} index - Item index to bring into view.
8287
8070
  * @returns {void}
8288
8071
  */
8289
- scrollToIndex(index) {
8072
+ scrollToIndex(index, behavior = "smooth") {
8290
8073
  const count = this.adapter?.itemCount?.() ?? 0;
8291
8074
  if (count <= 0)
8292
8075
  return;
8293
8076
  const topInContainer = this.offsetTopOf(index);
8294
8077
  const containerTop = this.containerTopInScroll();
8295
- const target = containerTop + topInContainer;
8078
+ const stickyH = this.stickyTopHeight();
8079
+ const viewportH = Math.max(0, this.scrollEl.clientHeight - stickyH);
8080
+ // item height from cache, or current estimate for unmeasured items
8081
+ const est = this.getEstimate();
8082
+ const itemH = this.heightCache[index] ?? est;
8083
+ // Align item center to viewport center (below any sticky header).
8084
+ // viewportH already excludes stickyH, so no further offset needed.
8085
+ // Equivalent to scrollIntoView({ block: "center" }).
8086
+ const centeredTarget = containerTop + topInContainer
8087
+ - (viewportH - itemH) / 3;
8296
8088
  const maxScroll = Math.max(0, this.scrollEl.scrollHeight - this.scrollEl.clientHeight);
8297
- this.scrollEl.scrollTop = Math.min(Math.max(0, target), maxScroll);
8089
+ const clamped = Math.min(Math.max(0, centeredTarget), maxScroll);
8090
+ this.scrollEl.scrollTo({ top: clamped, behavior });
8298
8091
  }
8299
8092
  /**
8300
8093
  * Disposes runtime resources without destroying the instance.
@@ -8313,7 +8106,7 @@
8313
8106
  this.scrollEl.removeEventListener("scroll", this.boundOnScroll);
8314
8107
  }
8315
8108
  this.resizeObs?.disconnect();
8316
- this.created.forEach(el => el.remove());
8109
+ this.created.forEach((el) => el.remove());
8317
8110
  this.created.clear();
8318
8111
  }
8319
8112
  /**
@@ -8361,9 +8154,22 @@
8361
8154
  if (count <= 0)
8362
8155
  return;
8363
8156
  this.suspend();
8364
- this.resetState();
8157
+ this.pendingScrollToIndex = null;
8158
+ // When visibility changes (search filter applied or cleared), heightCache may
8159
+ // contain heights measured while only a subset of items was visible. Re-using
8160
+ // these partial measurements in rebuildFenwick() causes incorrect prefix sums
8161
+ // (e.g. items measured while scrolled into a filtered window have real heights,
8162
+ // while surrounding items still use estimates — creating an uneven Fenwick).
8163
+ //
8164
+ // Safe fix: clear heightCache entirely on visibility change. The adaptive
8165
+ // estimator will re-seed from probeInitialHeight() on the next render, and
8166
+ // items will be re-measured as they scroll into view.
8167
+ this.heightCache = [];
8168
+ this.measuredSum = 0;
8169
+ this.measuredCount = 0;
8170
+ this.firstMeasured = false;
8171
+ this.resetDOM();
8365
8172
  this.cleanupInvisibleItems();
8366
- this.recomputeMeasuredStats(count);
8367
8173
  this.rebuildFenwick(count);
8368
8174
  this.start = 0;
8369
8175
  this.end = -1;
@@ -8381,7 +8187,30 @@
8381
8187
  }
8382
8188
  }
8383
8189
  /**
8384
- * Resets internal state: mounted elements, caches, Fenwick sums, padding, and estimator stats.
8190
+ * Resets DOM nodes, Fenwick sums, padding, and estimator stats — but preserves {@link heightCache}.
8191
+ *
8192
+ * Use this inside {@link refreshItem} so that {@link recomputeMeasuredStats} can still
8193
+ * read previously measured heights before the Fenwick tree is rebuilt.
8194
+ *
8195
+ * DOM side effects:
8196
+ * - Removes all currently mounted item elements tracked in {@link created}.
8197
+ * - Resets pad heights to `0px`.
8198
+ *
8199
+ * @returns {void}
8200
+ */
8201
+ resetDOM() {
8202
+ this.created.forEach((el) => el.remove());
8203
+ this.created.clear();
8204
+ this.fenwick.reset(0);
8205
+ this.PadTop.style.height = "0px";
8206
+ this.PadBottom.style.height = "0px";
8207
+ this.firstMeasured = false;
8208
+ }
8209
+ /**
8210
+ * Full reset: clears DOM nodes, Fenwick sums, padding, estimator stats, AND {@link heightCache}.
8211
+ *
8212
+ * Use this for complete teardown (e.g., adapter swap, destroy sequence) where all
8213
+ * cached measurements should be discarded.
8385
8214
  *
8386
8215
  * DOM side effects:
8387
8216
  * - Removes all currently mounted item elements tracked in {@link created}.
@@ -8390,7 +8219,7 @@
8390
8219
  * @returns {void}
8391
8220
  */
8392
8221
  resetState() {
8393
- this.created.forEach(el => el.remove());
8222
+ this.created.forEach((el) => el.remove());
8394
8223
  this.created.clear();
8395
8224
  this.heightCache = [];
8396
8225
  this.fenwick.reset(0);
@@ -8664,8 +8493,12 @@
8664
8493
  el.setAttribute(VirtualRecyclerView.ATTR_INDEX, String(index));
8665
8494
  const prev = el.previousElementSibling;
8666
8495
  const next = el.nextElementSibling;
8667
- const needsReorder = (prev && Number(prev.getAttribute(VirtualRecyclerView.ATTR_INDEX)) > index) ||
8668
- (next && Number(next.getAttribute(VirtualRecyclerView.ATTR_INDEX)) < index);
8496
+ const needsReorder = (prev &&
8497
+ Number(prev.getAttribute(VirtualRecyclerView.ATTR_INDEX)) >
8498
+ index) ||
8499
+ (next &&
8500
+ Number(next.getAttribute(VirtualRecyclerView.ATTR_INDEX)) <
8501
+ index);
8669
8502
  if (needsReorder) {
8670
8503
  el.remove();
8671
8504
  this.insertIntoHostByIndex(index, el);
@@ -8687,7 +8520,10 @@
8687
8520
  if (this.resizeObs)
8688
8521
  return;
8689
8522
  this.resizeObs = new ResizeObserver(() => {
8690
- if (this.suppressResize || this.suspended || !this.adapter || this.measureRaf != null)
8523
+ if (this.suppressResize ||
8524
+ this.suspended ||
8525
+ !this.adapter ||
8526
+ this.measureRaf != null)
8691
8527
  return;
8692
8528
  this.measureRaf = requestAnimationFrame(() => {
8693
8529
  this.measureRaf = null;
@@ -8728,6 +8564,15 @@
8728
8564
  this.rebuildFenwick(count);
8729
8565
  this.scheduleUpdateWindow();
8730
8566
  }
8567
+ // Corrective scroll: if ensureRendered() registered a target index, fire
8568
+ // scrollToIndex() now that real heights are in Fenwick. Clear the target
8569
+ // first to prevent infinite re-triggering (scrollToIndex may cause another
8570
+ // measure cycle, but heights won't change so changed === false next time).
8571
+ if (this.pendingScrollToIndex !== null) {
8572
+ const target = this.pendingScrollToIndex;
8573
+ this.pendingScrollToIndex = null;
8574
+ this.scrollToIndex(target, "smooth");
8575
+ }
8731
8576
  }
8732
8577
  /**
8733
8578
  * Scroll event handler. Schedules a window update on the next frame.
@@ -9016,31 +8861,6 @@
9016
8861
  * @internal
9017
8862
  */
9018
8863
  this.oldValue = null;
9019
- /**
9020
- * Root wrapper DOM node for the enhanced UI.
9021
- *
9022
- * Created during {@link init} via {@link Libs.mountNode}, inserted into the DOM during {@link mount},
9023
- * and removed during {@link destroy}.
9024
- */
9025
- this.node = null;
9026
- /**
9027
- * Parsed configuration (bound from the `<select>` element via binder map).
9028
- *
9029
- * Provides feature flags (multiple/disabled/readonly/visible/virtualScroll/ajax/autoclose…),
9030
- * a11y ids (e.g. `SEID_LIST`, `SEID_HOLDER`) and user callbacks under `options.on`.
9031
- *
9032
- * @internal
9033
- */
9034
- this.options = null;
9035
- /**
9036
- * Manager that owns model resources and bridges the Adapter ↔ RecyclerView pipeline.
9037
- *
9038
- * The configured adapter is {@link MixedAdapter}. The recyclerview implementation is chosen
9039
- * based on `options.virtualScroll` (standard {@link RecyclerView} vs {@link VirtualRecyclerView}).
9040
- *
9041
- * @internal
9042
- */
9043
- this.optionModelManager = null;
9044
8864
  /**
9045
8865
  * Whether the popup/list UI is currently open.
9046
8866
  *
@@ -9072,20 +8892,10 @@
9072
8892
  * @internal
9073
8893
  */
9074
8894
  this.hasDeInitialized = false;
9075
- /**
9076
- * Selective context (global helper / registry).
9077
- *
9078
- * Used to locate the instance wrapper via `Selective.find(...)` and to close other open instances.
9079
- */
9080
- this.Selective = null;
9081
8895
  /**
9082
8896
  * Registered plugins for this SelectBox instance.
9083
8897
  */
9084
8898
  this.plugins = [];
9085
- /**
9086
- * Cached plugin context for this SelectBox instance.
9087
- */
9088
- this.pluginContext = null;
9089
8899
  if (select && Selective)
9090
8900
  this.initialize(select, Selective);
9091
8901
  }
@@ -9210,7 +9020,9 @@
9210
9020
  classList: "seui-view",
9211
9021
  tabIndex: 0,
9212
9022
  onkeydown: (e) => {
9213
- if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
9023
+ if (e.key === "Enter" ||
9024
+ e.key === " " ||
9025
+ e.key === "ArrowDown") {
9214
9026
  e.preventDefault();
9215
9027
  this.getAction()?.open();
9216
9028
  }
@@ -9316,6 +9128,7 @@
9316
9128
  e.preventDefault();
9317
9129
  });
9318
9130
  Refresher.resizeBox(select, container.tags.ViewPanel);
9131
+ Refresher.resizeBox(select, this.node, true);
9319
9132
  select.classList.add("init");
9320
9133
  // initial mask
9321
9134
  const action = this.getAction();
@@ -9602,7 +9415,11 @@
9602
9415
  get value() {
9603
9416
  const item_list = this.valueArray;
9604
9417
  const valLength = item_list.length;
9605
- return valLength > 1 ? item_list : valLength === 0 ? "" : item_list[0];
9418
+ return valLength > 1
9419
+ ? item_list
9420
+ : valLength === 0
9421
+ ? ""
9422
+ : item_list[0];
9606
9423
  },
9607
9424
  get valueArray() {
9608
9425
  const item_list = [];
@@ -9627,7 +9444,7 @@
9627
9444
  get mask() {
9628
9445
  const item_list = [];
9629
9446
  superThis.getModelOption(true).forEach((m) => {
9630
- item_list.push(m.text);
9447
+ item_list.push(m.mask);
9631
9448
  });
9632
9449
  return item_list;
9633
9450
  },
@@ -9637,7 +9454,11 @@
9637
9454
  item_list.push(m.text);
9638
9455
  });
9639
9456
  const valLength = item_list.length;
9640
- return valLength > 1 ? item_list : valLength === 0 ? "" : item_list[0];
9457
+ return valLength > 1
9458
+ ? item_list
9459
+ : valLength === 0
9460
+ ? ""
9461
+ : item_list[0];
9641
9462
  },
9642
9463
  get isOpen() {
9643
9464
  return superThis.isOpen;
@@ -9662,7 +9483,8 @@
9662
9483
  },
9663
9484
  selectAll(_evtToken, trigger = true) {
9664
9485
  if (bindedOptions.multiple && bindedOptions.maxSelected > 0) {
9665
- if (superThis.getModelOption().length > bindedOptions.maxSelected)
9486
+ if (superThis.getModelOption().length >
9487
+ bindedOptions.maxSelected)
9666
9488
  return;
9667
9489
  }
9668
9490
  if (this.disabled || this.readonly || !bindedOptions.multiple)
@@ -9694,7 +9516,7 @@
9694
9516
  },
9695
9517
  deSelectByDataset(_evtToken, dataset, trigger = true) {
9696
9518
  if (dataset) {
9697
- superThis.getModelOption().forEach(optionModel => {
9519
+ superThis.getModelOption().forEach((optionModel) => {
9698
9520
  if (optionModel.dataset) {
9699
9521
  for (let searchKey in dataset) {
9700
9522
  let value = dataset[searchKey];
@@ -9708,12 +9530,14 @@
9708
9530
  this.change(false, trigger);
9709
9531
  }
9710
9532
  },
9711
- setValue(_evtToken = null, value, trigger = true, force = false) {
9533
+ setValue(_evtToken, value, trigger = true, force = false) {
9712
9534
  if (!Array.isArray(value))
9713
9535
  value = [value];
9714
9536
  value = value.filter((v) => v !== "" && v != null);
9715
9537
  if (value.length === 0) {
9716
- superThis.getModelOption().forEach((m) => (m.selectedNonTrigger = false));
9538
+ superThis
9539
+ .getModelOption()
9540
+ .forEach((m) => (m.selectedNonTrigger = false));
9717
9541
  this.change(false, trigger);
9718
9542
  return;
9719
9543
  }
@@ -9727,17 +9551,19 @@
9727
9551
  return;
9728
9552
  // AJAX: load missing values
9729
9553
  if (container.searchController?.isAjax?.()) {
9554
+ container.searchController.resetPagination();
9555
+ superThis.hasLoadedOnce = false;
9730
9556
  const { missing } = container.searchController.checkMissingValues(value);
9731
9557
  if (missing.length > 0) {
9732
9558
  (async () => {
9733
9559
  if (bindedOptions.loadingfield)
9734
9560
  container.popup?.showLoading?.();
9735
9561
  try {
9736
- container.searchController.resetPagination();
9737
9562
  const result = await container.searchController.loadByValues(missing);
9738
9563
  if (result.success && result.items.length > 0) {
9739
9564
  result.items.forEach((it) => {
9740
- if (missing.includes(it.value) || missing.includes(it.text))
9565
+ if (missing.includes(it.value) ||
9566
+ missing.includes(it.text))
9741
9567
  it.selected = true;
9742
9568
  });
9743
9569
  container.searchController.applyAjaxResult?.(result.items, false, false);
@@ -9748,6 +9574,10 @@
9748
9574
  }
9749
9575
  else if (missing.length > 0) {
9750
9576
  console.warn(`Could not load ${missing.length} values:`, missing);
9577
+ setTimeout(() => {
9578
+ container.searchController.resetPagination();
9579
+ this.change(false, trigger);
9580
+ }, 200);
9751
9581
  }
9752
9582
  }
9753
9583
  catch (error) {
@@ -9776,7 +9606,8 @@
9776
9606
  this.change(false, trigger);
9777
9607
  },
9778
9608
  load() {
9779
- if ((!superThis.hasLoadedOnce || superThis.isBeforeSearch) && bindedOptions?.ajax) {
9609
+ if ((!superThis.hasLoadedOnce || superThis.isBeforeSearch) &&
9610
+ bindedOptions?.ajax) {
9780
9611
  container.searchController.resetPagination();
9781
9612
  container.popup.showLoading();
9782
9613
  superThis.hasLoadedOnce = true;
@@ -9822,7 +9653,13 @@
9822
9653
  adapter.resetHighlight();
9823
9654
  }
9824
9655
  this.load();
9825
- container.popup.open(null, !container.popup.loadingState.isVisible);
9656
+ container.popup.open(() => {
9657
+ setTimeout(() => {
9658
+ if (selectedOption) {
9659
+ adapter.setHighlight(selectedOption, bindedOptions.autoscroll);
9660
+ }
9661
+ }, 100);
9662
+ }, !container.popup.loadingState.isVisible);
9826
9663
  container.searchbox.show();
9827
9664
  const ViewPanel = container.tags.ViewPanel;
9828
9665
  ViewPanel.setAttribute("aria-expanded", "true");
@@ -9863,9 +9700,10 @@
9863
9700
  else
9864
9701
  this.open();
9865
9702
  },
9866
- change(_evtToken = null, canTrigger = true) {
9703
+ change(_evtToken, canTrigger = true) {
9867
9704
  if (canTrigger) {
9868
- if (bindedOptions.multiple && bindedOptions.maxSelected > 0) {
9705
+ if (bindedOptions.multiple &&
9706
+ bindedOptions.maxSelected > 0) {
9869
9707
  if (this.valueArray.length > bindedOptions.maxSelected) {
9870
9708
  this.setValue(null, this.oldValue, false, true);
9871
9709
  }
@@ -9900,7 +9738,8 @@
9900
9738
  },
9901
9739
  refreshMask() {
9902
9740
  let mask = bindedOptions.placeholder;
9903
- if (!bindedOptions.multiple && superThis.getModelOption().length > 0) {
9741
+ if (!bindedOptions.multiple &&
9742
+ superThis.getModelOption().length > 0) {
9904
9743
  mask = this.mask[0];
9905
9744
  }
9906
9745
  mask ?? (mask = bindedOptions.placeholder);
@@ -9932,7 +9771,9 @@
9932
9771
  .search("")
9933
9772
  .then(() => {
9934
9773
  container.popup?.triggerResize?.();
9935
- resove(getInstance());
9774
+ setTimeout(() => {
9775
+ resove(getInstance());
9776
+ }, 60);
9936
9777
  })
9937
9778
  .catch((err) => {
9938
9779
  console.error("Initial ajax load error:", err);
@@ -9982,7 +9823,8 @@
9982
9823
  set(value) {
9983
9824
  superThis[privateProp] = !!value;
9984
9825
  if (superThis.container?.targetElement?.dataset) {
9985
- superThis.container.targetElement.dataset[prop] = String(!!value);
9826
+ superThis.container.targetElement.dataset[prop] =
9827
+ String(!!value);
9986
9828
  }
9987
9829
  },
9988
9830
  enumerable: true,
@@ -10010,7 +9852,7 @@
10010
9852
  * @returns A flat array of option models (possibly filtered).
10011
9853
  * @internal
10012
9854
  */
10013
- getModelOption(isSelected = null) {
9855
+ getModelOption(isSelected) {
10014
9856
  if (!this.optionModelManager)
10015
9857
  return [];
10016
9858
  const { modelList } = this.optionModelManager.getResources();
@@ -10090,14 +9932,6 @@
10090
9932
  * @internal
10091
9933
  */
10092
9934
  this.isActive = false;
10093
- /**
10094
- * Underlying DOM {@link MutationObserver} instance.
10095
- *
10096
- * `null` when disconnected.
10097
- *
10098
- * @internal
10099
- */
10100
- this.observer = null;
10101
9935
  /**
10102
9936
  * Registered detection callbacks.
10103
9937
  *
@@ -10691,7 +10525,7 @@
10691
10525
  if (wasObserving)
10692
10526
  this.EAObserver?.disconnect();
10693
10527
  bindMap.self?.deInit?.();
10694
- const wrapper = (bindMap.container?.element) ?? selectElement.parentElement;
10528
+ const wrapper = bindMap.container?.element ?? selectElement.parentElement;
10695
10529
  selectElement.style.display = "";
10696
10530
  selectElement.style.visibility = "";
10697
10531
  selectElement.disabled = false;
@@ -10908,7 +10742,7 @@
10908
10742
  if (typeof globalThis.GLOBAL_SEUI == "undefined") {
10909
10743
  const SECLASS = new Selective();
10910
10744
  globalThis.GLOBAL_SEUI = {
10911
- version: "1.4.0",
10745
+ version: "1.4.2",
10912
10746
  name: "SelectiveUI",
10913
10747
  bind: SECLASS.bind.bind(SECLASS),
10914
10748
  find: SECLASS.find.bind(SECLASS),
@@ -10916,7 +10750,7 @@
10916
10750
  effector: Effector.bind(Effector),
10917
10751
  rebind: SECLASS.rebind.bind(SECLASS),
10918
10752
  registerPlugin: SECLASS.registerPlugin.bind(SECLASS),
10919
- unregisterPlugin: SECLASS.unregisterPlugin.bind(SECLASS)
10753
+ unregisterPlugin: SECLASS.unregisterPlugin.bind(SECLASS),
10920
10754
  };
10921
10755
  let domInitialized = false;
10922
10756
  function init() {
@@ -10941,7 +10775,7 @@
10941
10775
  init();
10942
10776
  }
10943
10777
  }
10944
- console.log(`[${"SelectiveUI"}] v${"1.4.0"} loaded successfully`);
10778
+ console.log(`[${"SelectiveUI"}] v${"1.4.2"} loaded successfully`);
10945
10779
  }
10946
10780
  else {
10947
10781
  console.warn(`[${globalThis.GLOBAL_SEUI.name}] Already loaded (v${globalThis.GLOBAL_SEUI.version}). ` +
@@ -10998,7 +10832,7 @@
10998
10832
  * // Destroy all instances
10999
10833
  * destroy();
11000
10834
  */
11001
- function destroy(query = null) {
10835
+ function destroy(query) {
11002
10836
  globalThis.GLOBAL_SEUI.destroy(query);
11003
10837
  }
11004
10838
  /**