selective-ui 1.1.0 → 1.1.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 (42) hide show
  1. package/dist/selective-ui.esm.js +225 -200
  2. package/dist/selective-ui.esm.js.map +1 -1
  3. package/dist/selective-ui.esm.min.js +2 -1
  4. package/dist/selective-ui.esm.min.js.br +0 -0
  5. package/dist/selective-ui.min.js +2 -2
  6. package/dist/selective-ui.min.js.br +0 -0
  7. package/dist/selective-ui.umd.js +225 -200
  8. package/dist/selective-ui.umd.js.map +1 -1
  9. package/package.json +16 -15
  10. package/src/ts/adapter/mixed-adapter.ts +6 -7
  11. package/src/ts/components/accessorybox.ts +0 -10
  12. package/src/ts/components/directive.ts +0 -3
  13. package/src/ts/components/empty-state.ts +0 -3
  14. package/src/ts/components/loading-state.ts +3 -1
  15. package/src/ts/components/option-handle.ts +0 -5
  16. package/src/ts/components/placeholder.ts +0 -3
  17. package/src/ts/components/popup.ts +70 -11
  18. package/src/ts/components/searchbox.ts +2 -12
  19. package/src/ts/components/selectbox.ts +4 -2
  20. package/src/ts/core/base/adapter.ts +4 -5
  21. package/src/ts/core/base/model.ts +0 -1
  22. package/src/ts/core/base/recyclerview.ts +0 -6
  23. package/src/ts/core/base/view.ts +0 -24
  24. package/src/ts/core/model-manager.ts +3 -24
  25. package/src/ts/index.ts +10 -9
  26. package/src/ts/models/group-model.ts +0 -1
  27. package/src/ts/models/option-model.ts +3 -3
  28. package/src/ts/services/ea-observer.ts +0 -2
  29. package/src/ts/services/effector.ts +3 -4
  30. package/src/ts/services/refresher.ts +1 -1
  31. package/src/ts/services/resize-observer.ts +0 -1
  32. package/src/ts/services/select-observer.ts +0 -2
  33. package/src/ts/types/core/base/view.type.ts +0 -18
  34. package/src/ts/types/services/effector.type.ts +14 -0
  35. package/src/ts/types/utils/callback-scheduler.type.ts +69 -0
  36. package/src/ts/types/utils/libs.type.ts +0 -46
  37. package/src/ts/utils/callback-scheduler.ts +135 -0
  38. package/src/ts/utils/guard.ts +4 -10
  39. package/src/ts/utils/libs.ts +6 -77
  40. package/src/ts/utils/selective.ts +7 -5
  41. package/src/ts/views/group-view.ts +0 -1
  42. package/src/ts/views/option-view.ts +2 -0
@@ -1,4 +1,4 @@
1
- /*! Selective UI v1.1.0 | MIT License */
1
+ /*! Selective UI v1.1.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) :
@@ -72,6 +72,126 @@
72
72
  }
73
73
  }
74
74
 
75
+ class CallbackScheduler {
76
+ constructor() {
77
+ /**
78
+ * Stores callbacks by key in registration order.
79
+ *
80
+ * Notes:
81
+ * - Entries may become `undefined` after execution when `once` is enabled.
82
+ * This preserves indices so timer bookkeeping remains stable.
83
+ */
84
+ this.executeStored = new Map();
85
+ /**
86
+ * Per-key timer registry.
87
+ *
88
+ * - Outer Map: groups timers by `TimerKey`
89
+ * - Inner Map: maps callback index -> active timeout handle
90
+ *
91
+ * Each callback index has its own debounce timer, allowing independent scheduling.
92
+ */
93
+ this.timerRunner = new Map();
94
+ }
95
+ /**
96
+ * Registers a callback under a key.
97
+ *
98
+ * @param key - Group identifier for callbacks.
99
+ * @param callback - Function to execute after the debounce timeout.
100
+ * @param options - Scheduling options.
101
+ * @returns The index of the registered callback within its key bucket.
102
+ *
103
+ * Behavior:
104
+ * - Callbacks are stored in registration order.
105
+ * - `options.debounce` is treated as a per-callback delay (milliseconds).
106
+ * - `options.once` removes the entry after its first execution (index is preserved).
107
+ */
108
+ on(key, callback, options = {}) {
109
+ const timeout = options.debounce ?? 50;
110
+ const once = options.once ?? false;
111
+ if (!this.executeStored.has(key))
112
+ this.executeStored.set(key, []);
113
+ const bucket = this.executeStored.get(key);
114
+ bucket.push({ callback, timeout, once });
115
+ }
116
+ /**
117
+ * Removes all callbacks associated with a key and clears any active timers.
118
+ *
119
+ * @param key - Key whose callbacks and timers will be removed.
120
+ */
121
+ off(key) {
122
+ const runner = this.timerRunner.get(key);
123
+ if (runner) {
124
+ for (const t of runner.values())
125
+ clearTimeout(t);
126
+ runner.clear();
127
+ this.timerRunner.delete(key);
128
+ }
129
+ this.executeStored.delete(key);
130
+ }
131
+ /**
132
+ * Schedules execution for all callbacks registered under a key.
133
+ *
134
+ * @param key - Key whose callbacks will be scheduled.
135
+ * @param params - Parameters collected and passed as a single payload.
136
+ *
137
+ * Payload rules:
138
+ * - If `run(key)` is called without params, callbacks receive `null`.
139
+ * - If `run(key, ...params)` is called with params, callbacks receive `params` as an array.
140
+ *
141
+ * Debounce rules:
142
+ * - Each callback has its own timer (by index).
143
+ * - Calling `run()` again before the timeout clears the previous timer for that callback.
144
+ *
145
+ * Once rules:
146
+ * - If an entry has `once = true`, it is removed after execution by setting its slot to `undefined`.
147
+ * (The list is not spliced to preserve indices.)
148
+ */
149
+ run(key, ...params) {
150
+ const executes = this.executeStored.get(key);
151
+ if (!executes)
152
+ return;
153
+ if (!this.timerRunner.has(key))
154
+ this.timerRunner.set(key, new Map());
155
+ const runner = this.timerRunner.get(key);
156
+ for (let i = 0; i < executes.length; i++) {
157
+ const entry = executes[i];
158
+ if (!entry)
159
+ continue;
160
+ const prev = runner.get(i);
161
+ if (prev)
162
+ clearTimeout(prev);
163
+ const timer = setTimeout(() => {
164
+ entry.callback(params.length > 0 ? params : null);
165
+ if (entry.once) {
166
+ // Preserve index stability by leaving an empty slot.
167
+ executes[i] = undefined;
168
+ // Cleanup the timer handle for this index.
169
+ const current = runner.get(i);
170
+ if (current)
171
+ clearTimeout(current);
172
+ runner.delete(i);
173
+ }
174
+ }, entry.timeout);
175
+ runner.set(i, timer);
176
+ }
177
+ }
178
+ /**
179
+ * Clears callbacks and timers.
180
+ *
181
+ * @param key - When provided, clears only that key; otherwise clears all keys.
182
+ */
183
+ clear(key) {
184
+ if (key !== undefined) {
185
+ this.off(key);
186
+ return;
187
+ }
188
+ // Iterate over a snapshot of keys because `off()` mutates the maps.
189
+ for (const k of Array.from(this.executeStored.keys())) {
190
+ this.off(k);
191
+ }
192
+ }
193
+ }
194
+
75
195
  /**
76
196
  * @class
77
197
  */
@@ -86,18 +206,6 @@
86
206
  this._iStorage = new iStorage();
87
207
  return this._iStorage;
88
208
  }
89
- /**
90
- * Checks whether a value is null/undefined/empty-string/"0"/0.
91
- * Booleans are always considered non-empty.
92
- *
93
- * @param {unknown} value - The value to test.
94
- * @returns {boolean} - True if considered empty; otherwise false.
95
- */
96
- static isNullOrEmpty(value) {
97
- if (typeof value === "boolean")
98
- return false;
99
- return value == null || value === "" || value === 0 || value === "0";
100
- }
101
209
  /**
102
210
  * Deep-copies plain objects/arrays recursively. Returns primitives as-is.
103
211
  *
@@ -295,23 +403,6 @@
295
403
  }
296
404
  return recursiveTemp;
297
405
  }
298
- /**
299
- * Applies inline CSS styles to all matched elements. Accepts either a style
300
- * object or a single property + value pair.
301
- *
302
- * @param {string|NodeListOf<HTMLElement>|HTMLElement} queryCommon - Selector or element(s).
303
- * @param {Record<string, string>|string} styles - Style object or a single property name.
304
- * @param {string|null} [value=null] - Value for the single property form.
305
- */
306
- static setStyle(queryCommon, styles, value = null) {
307
- const apply_styles = typeof styles === "string" ? { [styles]: value } : { ...styles };
308
- const queryItems = this.getElements(queryCommon);
309
- for (let i = 0; i < queryItems.length; i++) {
310
- const item = queryItems[i];
311
- if (item)
312
- Object.assign(item.style, apply_styles);
313
- }
314
- }
315
406
  /**
316
407
  * Builds a configuration object by copying defaults and then overriding with
317
408
  * matching element properties or data-* attributes when present.
@@ -596,41 +687,7 @@
596
687
  * Schedules and batches function executions keyed by name, with debounced timers.
597
688
  * Provides setExecute(), clearExecute(), and run() to manage deferred callbacks.
598
689
  */
599
- Libs.timerProcess = {
600
- executeStored: {},
601
- timerRunner: {},
602
- setExecute(keyExecute, execute, timeout = 50, once = false) {
603
- if (!this.executeStored[keyExecute])
604
- this.executeStored[keyExecute] = [];
605
- this.executeStored[keyExecute].push({ execute, timeout, once });
606
- },
607
- clearExecute(keyExecute) {
608
- delete this.executeStored[keyExecute];
609
- },
610
- run(keyExecute, ...params) {
611
- const executes = this.executeStored[keyExecute];
612
- if (!executes)
613
- return;
614
- if (!this.timerRunner[keyExecute])
615
- this.timerRunner[keyExecute] = {};
616
- for (const key in executes) {
617
- const entry = executes[Number(key)];
618
- if (!entry)
619
- continue;
620
- if (!this.timerRunner[keyExecute][key]) {
621
- // placeholder, will be overwritten by setTimeout
622
- this.timerRunner[keyExecute][key] = setTimeout(() => { }, 0);
623
- clearTimeout(this.timerRunner[keyExecute][key]);
624
- }
625
- clearTimeout(this.timerRunner[keyExecute][key]);
626
- this.timerRunner[keyExecute][key] = setTimeout(() => {
627
- entry.execute(params.length > 0 ? params : null);
628
- if (entry.once)
629
- delete this.executeStored[keyExecute][Number(key)];
630
- }, entry.timeout);
631
- }
632
- },
633
- };
690
+ Libs.callbackScheduler = new CallbackScheduler();
634
691
 
635
692
  /**
636
693
  * Provides a lightweight event utility with cancel/continue control:
@@ -749,7 +806,7 @@
749
806
  width = options.width;
750
807
  if (cfgHeight > 0)
751
808
  height = options.height;
752
- Libs.setStyle(view, { width, height, minWidth, minHeight });
809
+ Object.assign(view.style, { width, height, minWidth, minHeight });
753
810
  }
754
811
  }
755
812
 
@@ -762,9 +819,6 @@
762
819
  * Supports HTML content based on configuration and provides methods to get or set the placeholder value.
763
820
  */
764
821
  constructor(options) {
765
- /**
766
- * @type {HTMLElement | null}
767
- */
768
822
  this.node = null;
769
823
  this._options = null;
770
824
  if (options)
@@ -849,14 +903,9 @@
849
903
  */
850
904
  constructor(options = null) {
851
905
  this.nodeMounted = null;
852
- /**
853
- * @type {HTMLDivElement | null}
854
- */
855
906
  this.node = null;
856
907
  this.options = null;
857
- /** @type {Function[]} */
858
908
  this._ActionOnSelectAll = [];
859
- /** @type {Function[]} */
860
909
  this._ActionOnDeSelectAll = [];
861
910
  if (options)
862
911
  this.init(options);
@@ -965,9 +1014,6 @@
965
1014
  * Provides methods to show/hide the state and check its visibility.
966
1015
  */
967
1016
  constructor(options = null) {
968
- /**
969
- * @type {HTMLDivElement | null}
970
- */
971
1017
  this.node = null;
972
1018
  this.options = null;
973
1019
  if (options)
@@ -1018,13 +1064,15 @@
1018
1064
  }
1019
1065
  }
1020
1066
 
1067
+ /**
1068
+ * @class
1069
+ */
1021
1070
  class LoadingState {
1022
1071
  /**
1023
1072
  * Represents a loading state component that displays a loading message during data fetch or processing.
1024
1073
  * Provides methods to show/hide the state and check its visibility.
1025
1074
  */
1026
1075
  constructor(options = null) {
1027
- /** @type {HTMLDivElement | null} */
1028
1076
  this.node = null;
1029
1077
  this.options = null;
1030
1078
  if (options)
@@ -1086,7 +1134,6 @@
1086
1134
  constructor() {
1087
1135
  this.isInit = false;
1088
1136
  this.element = null;
1089
- /** @type {ResizeObserver|null} */
1090
1137
  this._resizeObserver = null;
1091
1138
  this._mutationObserver = null;
1092
1139
  this.isInit = true;
@@ -1216,27 +1263,17 @@
1216
1263
  constructor(select = null, options = null, modelManager = null) {
1217
1264
  this.options = null;
1218
1265
  this.isCreated = false;
1219
- /** @type {MixedAdapter | null} */
1220
1266
  this.optionAdapter = null;
1221
- /** @type {HTMLDivElement | null} */
1222
1267
  this.node = null;
1223
- /** @type {EffectorInterface | null} */
1224
1268
  this._effSvc = null;
1225
- /** @type {ResizeObserverService | null} */
1226
1269
  this._resizeObser = null;
1227
1270
  this._parent = null;
1228
- /** @type {OptionHandle | null} */
1229
1271
  this.optionHandle = null;
1230
- /** @type {EmptyState | null} */
1231
1272
  this.emptyState = null;
1232
- /** @type {LoadingState | null} */
1233
1273
  this.loadingState = null;
1234
- /** @type {RecyclerViewContract<MixedAdapter> | null} */
1235
1274
  this.recyclerView = null;
1236
- /** @type {HTMLDivElement | null} */
1237
1275
  this._optionsContainer = null;
1238
1276
  this._scrollListener = null;
1239
- /** @type {ReturnType<typeof setTimeout> | null} */
1240
1277
  this._hideLoadHandle = null;
1241
1278
  this._modelManager = modelManager;
1242
1279
  if (select && options) {
@@ -1455,6 +1492,9 @@
1455
1492
  /**
1456
1493
  * Enables infinite scroll by listening to container scroll events and loading more data
1457
1494
  * when nearing the bottom, respecting pagination state (enabled/loading/hasMore).
1495
+ *
1496
+ * @param searchController - Provides pagination state and a method to load more items.
1497
+ * @param _options - Optional SelectiveOptions (reserved for future behavior tuning).
1458
1498
  */
1459
1499
  setupInfiniteScroll(searchController, _options) {
1460
1500
  if (!this.node)
@@ -1479,6 +1519,65 @@
1479
1519
  };
1480
1520
  this.node.addEventListener("scroll", this._scrollListener);
1481
1521
  }
1522
+ /**
1523
+ * Completely tear down the popup instance and release all resources.
1524
+ *
1525
+ * Responsibilities:
1526
+ * - Clear any pending timeouts and cancel animations/effects.
1527
+ * - Remove event listeners (scroll, mousedown) and disconnect ResizeObserver.
1528
+ * - Unmount and remove the DOM node; sever references to Effector/ModelManager.
1529
+ * - Dispose adapter/recycler and child components (OptionHandle, EmptyState, LoadingState).
1530
+ * - Reset flags and null out references to avoid memory leaks.
1531
+ *
1532
+ * Safe to call multiple times; all operations are guarded via optional chaining.
1533
+ */
1534
+ detroy() {
1535
+ if (this._hideLoadHandle) {
1536
+ clearTimeout(this._hideLoadHandle);
1537
+ this._hideLoadHandle = null;
1538
+ }
1539
+ if (this.node && this._scrollListener) {
1540
+ this.node.removeEventListener("scroll", this._scrollListener);
1541
+ this._scrollListener = null;
1542
+ }
1543
+ try {
1544
+ this._resizeObser?.disconnect();
1545
+ }
1546
+ catch (_) { }
1547
+ this._resizeObser = null;
1548
+ try {
1549
+ this._effSvc?.setElement?.(null);
1550
+ }
1551
+ catch (_) { }
1552
+ this._effSvc = null;
1553
+ if (this.node) {
1554
+ try {
1555
+ const clone = this.node.cloneNode(true);
1556
+ this.node.replaceWith(clone);
1557
+ clone.remove();
1558
+ }
1559
+ catch (_) {
1560
+ this.node.remove();
1561
+ }
1562
+ }
1563
+ this.node = null;
1564
+ this._optionsContainer = null;
1565
+ try {
1566
+ this._modelManager?.skipEvent?.(false);
1567
+ this.recyclerView?.clear?.();
1568
+ this.recyclerView = null;
1569
+ this.optionAdapter = null;
1570
+ this.node.remove();
1571
+ }
1572
+ catch (_) { }
1573
+ this._modelManager = null;
1574
+ this.optionHandle = null;
1575
+ this.emptyState = null;
1576
+ this.loadingState = null;
1577
+ this._parent = null;
1578
+ this.options = null;
1579
+ this.isCreated = false;
1580
+ }
1482
1581
  /**
1483
1582
  * Computes the parent panel's location and box metrics, including size, position,
1484
1583
  * padding, and border, accounting for iOS visual viewport offsets.
@@ -1584,21 +1683,9 @@
1584
1683
  * @param {object|null} [options=null] - Configuration (e.g., placeholder, accessibility IDs).
1585
1684
  */
1586
1685
  constructor(options = null) {
1587
- /**
1588
- * @type {MountViewResult<any> | null}
1589
- */
1590
1686
  this.nodeMounted = null;
1591
- /**
1592
- * @type {HTMLDivElement | null}
1593
- */
1594
1687
  this.node = null;
1595
- /**
1596
- * @type {HTMLInputElement | null}
1597
- */
1598
1688
  this.SearchInput = null;
1599
- /**
1600
- * @type {Function|null}
1601
- */
1602
1689
  this.onSearch = null;
1603
1690
  this.options = null;
1604
1691
  this.onNavigate = null;
@@ -2005,7 +2092,9 @@
2005
2092
  }
2006
2093
  else {
2007
2094
  this._resizeTimeout = setTimeout(() => {
2008
- this.element.style.transition = "none";
2095
+ if (this.element?.style) {
2096
+ this.element.style.transition = "none";
2097
+ }
2009
2098
  }, duration);
2010
2099
  }
2011
2100
  Object.assign(this.element.style, styles);
@@ -2059,7 +2148,6 @@
2059
2148
  constructor(options, targetElement = null, view = null) {
2060
2149
  /** @type {TTarget | null} */
2061
2150
  this.targetElement = null;
2062
- /** @type {TView | null} */
2063
2151
  this.view = null;
2064
2152
  this.position = -1;
2065
2153
  this.isInit = false;
@@ -2097,7 +2185,6 @@
2097
2185
  constructor(options, targetElement) {
2098
2186
  super(options, targetElement ?? null, null);
2099
2187
  this.label = "";
2100
- /** @type {OptionModel[]} */
2101
2188
  this.items = [];
2102
2189
  this.collapsed = false;
2103
2190
  this._privOnCollapsedChanged = [];
@@ -2295,7 +2382,7 @@
2295
2382
  * @type {boolean}
2296
2383
  */
2297
2384
  set selectedNonTrigger(value) {
2298
- const input = this.view?.getTag?.("OptionInput");
2385
+ const input = this.view?.view?.tags?.OptionInput;
2299
2386
  const viewEl = this.view?.getView?.();
2300
2387
  if (input)
2301
2388
  input.checked = value;
@@ -2390,7 +2477,7 @@
2390
2477
  onTargetChanged() {
2391
2478
  if (!this.view)
2392
2479
  return;
2393
- const labelContent = this.view.getTag("LabelContent");
2480
+ const labelContent = this.view.view.tags.LabelContent;
2394
2481
  if (labelContent) {
2395
2482
  if (this.options.allowHtml) {
2396
2483
  labelContent.innerHTML = this.text;
@@ -2399,7 +2486,7 @@
2399
2486
  labelContent.textContent = this.textContent;
2400
2487
  }
2401
2488
  }
2402
- const imageTag = this.view.getTag("OptionImage");
2489
+ const imageTag = this.view.view.tags.OptionImage;
2403
2490
  if (imageTag && this.hasImage) {
2404
2491
  imageTag.src = this.imageSrc;
2405
2492
  imageTag.alt = this.text;
@@ -2576,7 +2663,6 @@
2576
2663
  });
2577
2664
  let currentGroup = null;
2578
2665
  let position = 0;
2579
- const changesToApply = [];
2580
2666
  modelData.forEach((data) => {
2581
2667
  if (data.tagName === "OPTGROUP") {
2582
2668
  const dataVset = data;
@@ -2585,7 +2671,7 @@
2585
2671
  // Label is used as key; keep original behavior.
2586
2672
  const hasLabelChange = existingGroup.label !== dataVset.label;
2587
2673
  if (hasLabelChange) {
2588
- changesToApply.push(() => existingGroup.update(dataVset));
2674
+ existingGroup.update(dataVset);
2589
2675
  }
2590
2676
  existingGroup.position = position;
2591
2677
  existingGroup.items = [];
@@ -2605,17 +2691,8 @@
2605
2691
  const key = `${dataVset.value}::${dataVset.text}`;
2606
2692
  const existingOption = oldOptionMap.get(key);
2607
2693
  if (existingOption) {
2608
- const hasSelectedChange = existingOption.selected !== dataVset.selected;
2609
- const hasPositionChange = existingOption.position !== position;
2610
- if (hasSelectedChange || hasPositionChange) {
2611
- changesToApply.push(() => {
2612
- existingOption.update(dataVset);
2613
- existingOption.position = position;
2614
- });
2615
- }
2616
- else {
2617
- existingOption.position = position;
2618
- }
2694
+ existingOption.update(dataVset);
2695
+ existingOption.position = position;
2619
2696
  const parentGroup = dataVset["__parentGroup"];
2620
2697
  if (parentGroup && currentGroup) {
2621
2698
  currentGroup.addItem(existingOption);
@@ -2642,11 +2719,6 @@
2642
2719
  position++;
2643
2720
  }
2644
2721
  });
2645
- if (changesToApply.length > 0) {
2646
- requestAnimationFrame(() => {
2647
- changesToApply.forEach((change) => change());
2648
- });
2649
- }
2650
2722
  oldGroupMap.forEach((removedGroup) => {
2651
2723
  removedGroup.view?.getView?.()?.remove?.();
2652
2724
  });
@@ -2689,9 +2761,6 @@
2689
2761
  * adapter instance, and recycler view instance.
2690
2762
  */
2691
2763
  getResources() {
2692
- if (!this._privAdapterHandle || !this._privRecyclerViewHandle) {
2693
- throw new Error("ModelManager resources not loaded. Call load() first.");
2694
- }
2695
2764
  return {
2696
2765
  modelList: this._privModelList,
2697
2766
  adapter: this._privAdapterHandle,
@@ -2726,13 +2795,7 @@
2726
2795
  * @param {HTMLDivElement|null} [viewElement=null] - The root element where the adapter will render items.
2727
2796
  */
2728
2797
  constructor(viewElement = null) {
2729
- /**
2730
- * @type {HTMLDivElement|null}
2731
- */
2732
2798
  this.viewElement = null;
2733
- /**
2734
- * @type {TAdapter|null}
2735
- */
2736
2799
  this.adapter = null;
2737
2800
  this.viewElement = viewElement;
2738
2801
  }
@@ -2799,20 +2862,11 @@
2799
2862
  * @param {object|null} options - Configuration options for the accessory box (e.g., layout and behavior).
2800
2863
  */
2801
2864
  constructor(options = null) {
2802
- /**
2803
- * @type {MountViewResult<any> | null}
2804
- */
2805
2865
  this.nodeMounted = null;
2806
- /**
2807
- * @type {HTMLDivElement | null}
2808
- */
2809
2866
  this.node = null;
2810
2867
  this.options = null;
2811
- /** @type {HTMLDivElement | null} */
2812
2868
  this.selectUIMask = null;
2813
- /** @type {HTMLDivElement | null} */
2814
2869
  this.parentMask = null;
2815
- /** @type {ModelManager<MixedItem> | null} */
2816
2870
  this.modelManager = null;
2817
2871
  if (options)
2818
2872
  this.init(options);
@@ -3513,7 +3567,6 @@
3513
3567
  * @param {TItem[]} [items=[]] - Initial items to be managed by the adapter.
3514
3568
  */
3515
3569
  constructor(items = []) {
3516
- /** @type {TItem[]} */
3517
3570
  this.items = [];
3518
3571
  this.adapterKey = Libs.randomString(12);
3519
3572
  this.isSkipEvent = false;
@@ -3550,7 +3603,7 @@
3550
3603
  * @param {Function} callback - Function to execute before the property changes.
3551
3604
  */
3552
3605
  onPropChanging(propName, callback) {
3553
- Libs.timerProcess.setExecute(`${propName}ing_${this.adapterKey}`, callback, 1);
3606
+ Libs.callbackScheduler.on(`${propName}ing_${this.adapterKey}`, callback, { debounce: 1 });
3554
3607
  }
3555
3608
  /**
3556
3609
  * Registers a post-change callback for a property change pipeline.
@@ -3560,7 +3613,7 @@
3560
3613
  * @param {Function} callback - Function to execute after the property changes.
3561
3614
  */
3562
3615
  onPropChanged(propName, callback) {
3563
- Libs.timerProcess.setExecute(`${propName}_${this.adapterKey}`, callback);
3616
+ Libs.callbackScheduler.on(`${propName}_${this.adapterKey}`, callback);
3564
3617
  }
3565
3618
  /**
3566
3619
  * Triggers the post-change pipeline for a given property, passing optional parameters
@@ -3570,7 +3623,7 @@
3570
3623
  * @param {...any} params - Parameters forwarded to the callbacks.
3571
3624
  */
3572
3625
  changeProp(propName, ...params) {
3573
- Libs.timerProcess.run(`${propName}_${this.adapterKey}`, ...params);
3626
+ Libs.callbackScheduler.run(`${propName}_${this.adapterKey}`, ...params);
3574
3627
  }
3575
3628
  /**
3576
3629
  * Triggers the pre-change pipeline for a given property, passing optional parameters
@@ -3580,7 +3633,7 @@
3580
3633
  * @param {...any} params - Parameters forwarded to the callbacks.
3581
3634
  */
3582
3635
  changingProp(propName, ...params) {
3583
- Libs.timerProcess.run(`${propName}ing_${this.adapterKey}`, ...params);
3636
+ Libs.callbackScheduler.run(`${propName}ing_${this.adapterKey}`, ...params);
3584
3637
  }
3585
3638
  /**
3586
3639
  * Creates and returns a viewer instance for the given item within the specified parent container.
@@ -3660,9 +3713,7 @@
3660
3713
  * @param {HTMLElement} parent - The parent element into which this view will render.
3661
3714
  */
3662
3715
  constructor(parent) {
3663
- /** @type {HTMLElement|null} */
3664
3716
  this.parent = null;
3665
- /** @type {MountViewResult<TTags> | null} */
3666
3717
  this.view = null;
3667
3718
  this.parent = parent;
3668
3719
  }
@@ -3686,28 +3737,6 @@
3686
3737
  throw new Error("View is not mounted. Did you forget to set this.view?");
3687
3738
  return this.view.view;
3688
3739
  }
3689
- /**
3690
- * Retrieves a single tagged element from the mounted view.
3691
- *
3692
- * @template K
3693
- * @param {K} tag - The tag key corresponding to the desired element.
3694
- * @returns {TTags[K]} - The element associated with the provided tag key.
3695
- */
3696
- getTag(tag) {
3697
- if (!this.view)
3698
- throw new Error("View is not mounted. Did you forget to set this.view?");
3699
- return this.view.tags[tag];
3700
- }
3701
- /**
3702
- * Retrieves the full tag map for the mounted view.
3703
- *
3704
- * @returns {TTags} - An object map of all tagged elements.
3705
- */
3706
- getTags() {
3707
- if (!this.view)
3708
- throw new Error("View is not mounted. Did you forget to set this.view?");
3709
- return this.view.tags;
3710
- }
3711
3740
  }
3712
3741
 
3713
3742
  /**
@@ -3716,7 +3745,6 @@
3716
3745
  class GroupView extends View {
3717
3746
  constructor() {
3718
3747
  super(...arguments);
3719
- /** @type {GroupViewResult} */
3720
3748
  this.view = null;
3721
3749
  }
3722
3750
  /**
@@ -4164,7 +4192,7 @@
4164
4192
  _handleGroupView(groupModel, groupView, position) {
4165
4193
  super.onViewHolder(groupModel, groupView, position);
4166
4194
  groupModel.view = groupView;
4167
- const header = groupView.getTag("GroupHeader");
4195
+ const header = groupView.view.tags.GroupHeader;
4168
4196
  header.textContent = groupModel.label;
4169
4197
  if (!groupModel.isInit) {
4170
4198
  header.style.cursor = "pointer";
@@ -4216,7 +4244,7 @@
4216
4244
  }
4217
4245
  optionModel.view = optionViewer;
4218
4246
  if (optionModel.hasImage) {
4219
- const imageTag = optionViewer.getTag("OptionImage");
4247
+ const imageTag = optionViewer.view.tags.OptionImage;
4220
4248
  if (imageTag) {
4221
4249
  if (imageTag.src !== optionModel.imageSrc)
4222
4250
  imageTag.src = optionModel.imageSrc;
@@ -4224,9 +4252,9 @@
4224
4252
  imageTag.alt = optionModel.text;
4225
4253
  }
4226
4254
  }
4227
- optionViewer.getTag("LabelContent").innerHTML = optionModel.text;
4255
+ optionViewer.view.tags.LabelContent.innerHTML = optionModel.text;
4228
4256
  if (!optionModel.isInit) {
4229
- optionViewer.getTag("OptionView").addEventListener("click", (ev) => {
4257
+ optionViewer.view.tags.OptionView.addEventListener("click", (ev) => {
4230
4258
  ev.stopPropagation();
4231
4259
  ev.preventDefault();
4232
4260
  if (this.isSkipEvent)
@@ -4246,8 +4274,8 @@
4246
4274
  }, 5);
4247
4275
  }
4248
4276
  });
4249
- optionViewer.getTag("OptionView").title = optionModel.textContent;
4250
- optionViewer.getTag("OptionView").addEventListener("mouseenter", () => {
4277
+ optionViewer.view.tags.OptionView.title = optionModel.textContent;
4278
+ optionViewer.view.tags.OptionView.addEventListener("mouseenter", () => {
4251
4279
  if (this.isSkipEvent)
4252
4280
  return;
4253
4281
  this.setHighlight(this.flatOptions.indexOf(optionModel), false);
@@ -4470,10 +4498,8 @@
4470
4498
  constructor(select = null, Selective = null) {
4471
4499
  this.container = {};
4472
4500
  this.oldValue = null;
4473
- /** @type {HTMLDivElement|null} */
4474
4501
  this.node = null;
4475
4502
  this.options = null;
4476
- /** @type {ModelManager<MixedItem, MixedAdapter> | null} */
4477
4503
  this.optionModelManager = null;
4478
4504
  this.isOpen = false;
4479
4505
  this.hasLoadedOnce = false;
@@ -5050,6 +5076,9 @@
5050
5076
  }
5051
5077
  return flatOptions;
5052
5078
  }
5079
+ detroy() {
5080
+ this.container.popup.detroy();
5081
+ }
5053
5082
  }
5054
5083
 
5055
5084
  /**
@@ -5058,9 +5087,7 @@
5058
5087
  class ElementAdditionObserver {
5059
5088
  constructor() {
5060
5089
  this._isActive = false;
5061
- /** @type {MutationObserver|null} */
5062
5090
  this._observer = null;
5063
- /** @type {Array<(el: T) => void>} */
5064
5091
  this._actions = [];
5065
5092
  }
5066
5093
  /**
@@ -5145,9 +5172,9 @@
5145
5172
  merged.on.load = (merged.on.load ?? []);
5146
5173
  this.bindedQueries.set(query, merged);
5147
5174
  const doneToken = Libs.randomString();
5148
- Libs.timerProcess.setExecute(doneToken, () => {
5175
+ Libs.callbackScheduler.on(doneToken, () => {
5149
5176
  iEvents.callEvent([this.find(query)], ...merged.on.load);
5150
- Libs.timerProcess.clearExecute(doneToken);
5177
+ Libs.callbackScheduler.clear(doneToken);
5151
5178
  merged.on.load = [];
5152
5179
  });
5153
5180
  const selectElements = Libs.getElements(query);
@@ -5156,7 +5183,7 @@
5156
5183
  if (item.tagName === "SELECT") {
5157
5184
  Libs.removeUnbinderMap(item);
5158
5185
  if (this.applySelectBox(item, merged)) {
5159
- Libs.timerProcess.run(doneToken);
5186
+ Libs.callbackScheduler.run(doneToken);
5160
5187
  }
5161
5188
  }
5162
5189
  })();
@@ -5281,6 +5308,8 @@
5281
5308
  const bindMap = Libs.getBinderMap(selectElement);
5282
5309
  if (!bindMap)
5283
5310
  return;
5311
+ const popup = bindMap.container?.popup;
5312
+ popup?.detroy();
5284
5313
  Libs.setUnbinderMap(selectElement, bindMap);
5285
5314
  const wasObserving = !!this.EAObserver;
5286
5315
  if (wasObserving)
@@ -5421,7 +5450,6 @@
5421
5450
 
5422
5451
  /**
5423
5452
  * Checks for a previously loaded global library instance by name.
5424
- * If found (with `__loaded` flag), logs a warning and returns true; otherwise
5425
5453
  * initializes a loading placeholder on `window[name]` and returns false.
5426
5454
  *
5427
5455
  * @param {string} LIB_NAME - The global namespace key to check on `window`.
@@ -5431,13 +5459,12 @@
5431
5459
  if (typeof window === "undefined")
5432
5460
  return false;
5433
5461
  const existing = window[LIB_NAME];
5434
- if (existing && existing.__loaded) {
5435
- console.warn(`[${LIB_NAME}] Already loaded (v${existing.__version}). ` +
5462
+ if (existing) {
5463
+ console.warn(`[${LIB_NAME}] Already loaded (v${existing.version}). ` +
5436
5464
  `Using existing instance. Please remove duplicate <script> tags.`);
5437
5465
  return true;
5438
5466
  }
5439
5467
  const base = existing ?? {};
5440
- base.__loading = true;
5441
5468
  window[LIB_NAME] = base;
5442
5469
  return false;
5443
5470
  }
@@ -5455,16 +5482,14 @@
5455
5482
  if (typeof window === "undefined")
5456
5483
  return;
5457
5484
  const ns = (window[name] ?? {});
5458
- ns.__loaded = true;
5459
- ns.__loading = false;
5460
- ns.__version = version;
5485
+ ns.version = version;
5461
5486
  Object.assign(ns, api);
5462
5487
  Object.freeze(ns);
5463
5488
  window[name] = ns;
5464
5489
  console.log(`[${name}] v${version} loaded successfully`);
5465
5490
  }
5466
5491
 
5467
- const version = "1.1.0";
5492
+ const version = "1.1.2";
5468
5493
  const name = "SelectiveUI";
5469
5494
  const alreadyLoaded = checkDuplicate(name);
5470
5495
  function getGlobal() {
@@ -5496,7 +5521,7 @@
5496
5521
  * Destroys Selective instances associated with the given query.
5497
5522
  * Proxies to a global loaded instance if available; otherwise uses local Selective.destroy.
5498
5523
  */
5499
- function destroy(query) {
5524
+ function destroy(query = null) {
5500
5525
  const global = getGlobal();
5501
5526
  if (alreadyLoaded && global)
5502
5527
  return global.destroy(query);
@@ -5523,11 +5548,13 @@
5523
5548
  return Effector(element);
5524
5549
  }
5525
5550
  if (!alreadyLoaded) {
5526
- let initialized = false;
5551
+ const api = { bind, find, destroy, rebind, effector, version };
5552
+ markLoaded(name, version, api);
5553
+ let domInitialized = false;
5527
5554
  function init() {
5528
- if (initialized)
5555
+ if (domInitialized)
5529
5556
  return;
5530
- initialized = true;
5557
+ domInitialized = true;
5531
5558
  document.addEventListener("mousedown", () => {
5532
5559
  const sels = Libs.getBindedCommand();
5533
5560
  if (sels.length > 0) {
@@ -5537,8 +5564,6 @@
5537
5564
  }
5538
5565
  });
5539
5566
  Selective.Observer();
5540
- const api = { bind, find, destroy, rebind, effector, version };
5541
- markLoaded(name, version, api);
5542
5567
  }
5543
5568
  if (document.readyState === "loading") {
5544
5569
  document.addEventListener("DOMContentLoaded", init);