selective-ui 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1449,6 +1449,9 @@ class Popup {
1449
1449
  /**
1450
1450
  * Enables infinite scroll by listening to container scroll events and loading more data
1451
1451
  * when nearing the bottom, respecting pagination state (enabled/loading/hasMore).
1452
+ *
1453
+ * @param searchController - Provides pagination state and a method to load more items.
1454
+ * @param _options - Optional SelectiveOptions (reserved for future behavior tuning).
1452
1455
  */
1453
1456
  setupInfiniteScroll(searchController, _options) {
1454
1457
  if (!this.node)
@@ -1473,6 +1476,65 @@ class Popup {
1473
1476
  };
1474
1477
  this.node.addEventListener("scroll", this._scrollListener);
1475
1478
  }
1479
+ /**
1480
+ * Completely tear down the popup instance and release all resources.
1481
+ *
1482
+ * Responsibilities:
1483
+ * - Clear any pending timeouts and cancel animations/effects.
1484
+ * - Remove event listeners (scroll, mousedown) and disconnect ResizeObserver.
1485
+ * - Unmount and remove the DOM node; sever references to Effector/ModelManager.
1486
+ * - Dispose adapter/recycler and child components (OptionHandle, EmptyState, LoadingState).
1487
+ * - Reset flags and null out references to avoid memory leaks.
1488
+ *
1489
+ * Safe to call multiple times; all operations are guarded via optional chaining.
1490
+ */
1491
+ detroy() {
1492
+ if (this._hideLoadHandle) {
1493
+ clearTimeout(this._hideLoadHandle);
1494
+ this._hideLoadHandle = null;
1495
+ }
1496
+ if (this.node && this._scrollListener) {
1497
+ this.node.removeEventListener("scroll", this._scrollListener);
1498
+ this._scrollListener = null;
1499
+ }
1500
+ try {
1501
+ this._resizeObser?.disconnect();
1502
+ }
1503
+ catch (_) { }
1504
+ this._resizeObser = null;
1505
+ try {
1506
+ this._effSvc?.setElement?.(null);
1507
+ }
1508
+ catch (_) { }
1509
+ this._effSvc = null;
1510
+ if (this.node) {
1511
+ try {
1512
+ const clone = this.node.cloneNode(true);
1513
+ this.node.replaceWith(clone);
1514
+ clone.remove();
1515
+ }
1516
+ catch (_) {
1517
+ this.node.remove();
1518
+ }
1519
+ }
1520
+ this.node = null;
1521
+ this._optionsContainer = null;
1522
+ try {
1523
+ this._modelManager?.skipEvent?.(false);
1524
+ this.recyclerView?.clear?.();
1525
+ this.recyclerView = null;
1526
+ this.optionAdapter = null;
1527
+ this.node.remove();
1528
+ }
1529
+ catch (_) { }
1530
+ this._modelManager = null;
1531
+ this.optionHandle = null;
1532
+ this.emptyState = null;
1533
+ this.loadingState = null;
1534
+ this._parent = null;
1535
+ this.options = null;
1536
+ this.isCreated = false;
1537
+ }
1476
1538
  /**
1477
1539
  * Computes the parent panel's location and box metrics, including size, position,
1478
1540
  * padding, and border, accounting for iOS visual viewport offsets.
@@ -1999,7 +2061,9 @@ class EffectorImpl {
1999
2061
  }
2000
2062
  else {
2001
2063
  this._resizeTimeout = setTimeout(() => {
2002
- this.element.style.transition = "none";
2064
+ if (this.element?.style) {
2065
+ this.element.style.transition = "none";
2066
+ }
2003
2067
  }, duration);
2004
2068
  }
2005
2069
  Object.assign(this.element.style, styles);
@@ -2570,7 +2634,6 @@ class ModelManager {
2570
2634
  });
2571
2635
  let currentGroup = null;
2572
2636
  let position = 0;
2573
- const changesToApply = [];
2574
2637
  modelData.forEach((data) => {
2575
2638
  if (data.tagName === "OPTGROUP") {
2576
2639
  const dataVset = data;
@@ -2579,7 +2642,7 @@ class ModelManager {
2579
2642
  // Label is used as key; keep original behavior.
2580
2643
  const hasLabelChange = existingGroup.label !== dataVset.label;
2581
2644
  if (hasLabelChange) {
2582
- changesToApply.push(() => existingGroup.update(dataVset));
2645
+ existingGroup.update(dataVset);
2583
2646
  }
2584
2647
  existingGroup.position = position;
2585
2648
  existingGroup.items = [];
@@ -2599,17 +2662,8 @@ class ModelManager {
2599
2662
  const key = `${dataVset.value}::${dataVset.text}`;
2600
2663
  const existingOption = oldOptionMap.get(key);
2601
2664
  if (existingOption) {
2602
- const hasSelectedChange = existingOption.selected !== dataVset.selected;
2603
- const hasPositionChange = existingOption.position !== position;
2604
- if (hasSelectedChange || hasPositionChange) {
2605
- changesToApply.push(() => {
2606
- existingOption.update(dataVset);
2607
- existingOption.position = position;
2608
- });
2609
- }
2610
- else {
2611
- existingOption.position = position;
2612
- }
2665
+ existingOption.update(dataVset);
2666
+ existingOption.position = position;
2613
2667
  const parentGroup = dataVset["__parentGroup"];
2614
2668
  if (parentGroup && currentGroup) {
2615
2669
  currentGroup.addItem(existingOption);
@@ -2636,11 +2690,6 @@ class ModelManager {
2636
2690
  position++;
2637
2691
  }
2638
2692
  });
2639
- if (changesToApply.length > 0) {
2640
- requestAnimationFrame(() => {
2641
- changesToApply.forEach((change) => change());
2642
- });
2643
- }
2644
2693
  oldGroupMap.forEach((removedGroup) => {
2645
2694
  removedGroup.view?.getView?.()?.remove?.();
2646
2695
  });
@@ -2683,9 +2732,6 @@ class ModelManager {
2683
2732
  * adapter instance, and recycler view instance.
2684
2733
  */
2685
2734
  getResources() {
2686
- if (!this._privAdapterHandle || !this._privRecyclerViewHandle) {
2687
- throw new Error("ModelManager resources not loaded. Call load() first.");
2688
- }
2689
2735
  return {
2690
2736
  modelList: this._privModelList,
2691
2737
  adapter: this._privAdapterHandle,
@@ -5044,6 +5090,9 @@ class SelectBox {
5044
5090
  }
5045
5091
  return flatOptions;
5046
5092
  }
5093
+ detroy() {
5094
+ this.container.popup.detroy();
5095
+ }
5047
5096
  }
5048
5097
 
5049
5098
  /**
@@ -5275,6 +5324,8 @@ class Selective {
5275
5324
  const bindMap = Libs.getBinderMap(selectElement);
5276
5325
  if (!bindMap)
5277
5326
  return;
5327
+ const popup = bindMap.container?.popup;
5328
+ popup?.detroy();
5278
5329
  Libs.setUnbinderMap(selectElement, bindMap);
5279
5330
  const wasObserving = !!this.EAObserver;
5280
5331
  if (wasObserving)
@@ -5458,7 +5509,7 @@ function markLoaded(name, version, api) {
5458
5509
  console.log(`[${name}] v${version} loaded successfully`);
5459
5510
  }
5460
5511
 
5461
- const version = "1.1.0";
5512
+ const version = "1.1.1";
5462
5513
  const name = "SelectiveUI";
5463
5514
  const alreadyLoaded = checkDuplicate(name);
5464
5515
  function getGlobal() {
@@ -5490,7 +5541,7 @@ function find(query) {
5490
5541
  * Destroys Selective instances associated with the given query.
5491
5542
  * Proxies to a global loaded instance if available; otherwise uses local Selective.destroy.
5492
5543
  */
5493
- function destroy(query) {
5544
+ function destroy(query = null) {
5494
5545
  const global = getGlobal();
5495
5546
  if (alreadyLoaded && global)
5496
5547
  return global.destroy(query);