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.
- package/dist/selective-ui.esm.js +225 -200
- package/dist/selective-ui.esm.js.map +1 -1
- package/dist/selective-ui.esm.min.js +2 -1
- package/dist/selective-ui.esm.min.js.br +0 -0
- package/dist/selective-ui.min.js +2 -2
- package/dist/selective-ui.min.js.br +0 -0
- package/dist/selective-ui.umd.js +225 -200
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +16 -15
- package/src/ts/adapter/mixed-adapter.ts +6 -7
- package/src/ts/components/accessorybox.ts +0 -10
- package/src/ts/components/directive.ts +0 -3
- package/src/ts/components/empty-state.ts +0 -3
- package/src/ts/components/loading-state.ts +3 -1
- package/src/ts/components/option-handle.ts +0 -5
- package/src/ts/components/placeholder.ts +0 -3
- package/src/ts/components/popup.ts +70 -11
- package/src/ts/components/searchbox.ts +2 -12
- package/src/ts/components/selectbox.ts +4 -2
- package/src/ts/core/base/adapter.ts +4 -5
- package/src/ts/core/base/model.ts +0 -1
- package/src/ts/core/base/recyclerview.ts +0 -6
- package/src/ts/core/base/view.ts +0 -24
- package/src/ts/core/model-manager.ts +3 -24
- package/src/ts/index.ts +10 -9
- package/src/ts/models/group-model.ts +0 -1
- package/src/ts/models/option-model.ts +3 -3
- package/src/ts/services/ea-observer.ts +0 -2
- package/src/ts/services/effector.ts +3 -4
- package/src/ts/services/refresher.ts +1 -1
- package/src/ts/services/resize-observer.ts +0 -1
- package/src/ts/services/select-observer.ts +0 -2
- package/src/ts/types/core/base/view.type.ts +0 -18
- package/src/ts/types/services/effector.type.ts +14 -0
- package/src/ts/types/utils/callback-scheduler.type.ts +69 -0
- package/src/ts/types/utils/libs.type.ts +0 -46
- package/src/ts/utils/callback-scheduler.ts +135 -0
- package/src/ts/utils/guard.ts +4 -10
- package/src/ts/utils/libs.ts +6 -77
- package/src/ts/utils/selective.ts +7 -5
- package/src/ts/views/group-view.ts +0 -1
- package/src/ts/views/option-view.ts +2 -0
package/dist/selective-ui.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Selective UI v1.1.
|
|
1
|
+
/*! Selective UI v1.1.2 | MIT License */
|
|
2
2
|
/**
|
|
3
3
|
* @class
|
|
4
4
|
*/
|
|
@@ -66,6 +66,126 @@ class iStorage {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
class CallbackScheduler {
|
|
70
|
+
constructor() {
|
|
71
|
+
/**
|
|
72
|
+
* Stores callbacks by key in registration order.
|
|
73
|
+
*
|
|
74
|
+
* Notes:
|
|
75
|
+
* - Entries may become `undefined` after execution when `once` is enabled.
|
|
76
|
+
* This preserves indices so timer bookkeeping remains stable.
|
|
77
|
+
*/
|
|
78
|
+
this.executeStored = new Map();
|
|
79
|
+
/**
|
|
80
|
+
* Per-key timer registry.
|
|
81
|
+
*
|
|
82
|
+
* - Outer Map: groups timers by `TimerKey`
|
|
83
|
+
* - Inner Map: maps callback index -> active timeout handle
|
|
84
|
+
*
|
|
85
|
+
* Each callback index has its own debounce timer, allowing independent scheduling.
|
|
86
|
+
*/
|
|
87
|
+
this.timerRunner = new Map();
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Registers a callback under a key.
|
|
91
|
+
*
|
|
92
|
+
* @param key - Group identifier for callbacks.
|
|
93
|
+
* @param callback - Function to execute after the debounce timeout.
|
|
94
|
+
* @param options - Scheduling options.
|
|
95
|
+
* @returns The index of the registered callback within its key bucket.
|
|
96
|
+
*
|
|
97
|
+
* Behavior:
|
|
98
|
+
* - Callbacks are stored in registration order.
|
|
99
|
+
* - `options.debounce` is treated as a per-callback delay (milliseconds).
|
|
100
|
+
* - `options.once` removes the entry after its first execution (index is preserved).
|
|
101
|
+
*/
|
|
102
|
+
on(key, callback, options = {}) {
|
|
103
|
+
const timeout = options.debounce ?? 50;
|
|
104
|
+
const once = options.once ?? false;
|
|
105
|
+
if (!this.executeStored.has(key))
|
|
106
|
+
this.executeStored.set(key, []);
|
|
107
|
+
const bucket = this.executeStored.get(key);
|
|
108
|
+
bucket.push({ callback, timeout, once });
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Removes all callbacks associated with a key and clears any active timers.
|
|
112
|
+
*
|
|
113
|
+
* @param key - Key whose callbacks and timers will be removed.
|
|
114
|
+
*/
|
|
115
|
+
off(key) {
|
|
116
|
+
const runner = this.timerRunner.get(key);
|
|
117
|
+
if (runner) {
|
|
118
|
+
for (const t of runner.values())
|
|
119
|
+
clearTimeout(t);
|
|
120
|
+
runner.clear();
|
|
121
|
+
this.timerRunner.delete(key);
|
|
122
|
+
}
|
|
123
|
+
this.executeStored.delete(key);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Schedules execution for all callbacks registered under a key.
|
|
127
|
+
*
|
|
128
|
+
* @param key - Key whose callbacks will be scheduled.
|
|
129
|
+
* @param params - Parameters collected and passed as a single payload.
|
|
130
|
+
*
|
|
131
|
+
* Payload rules:
|
|
132
|
+
* - If `run(key)` is called without params, callbacks receive `null`.
|
|
133
|
+
* - If `run(key, ...params)` is called with params, callbacks receive `params` as an array.
|
|
134
|
+
*
|
|
135
|
+
* Debounce rules:
|
|
136
|
+
* - Each callback has its own timer (by index).
|
|
137
|
+
* - Calling `run()` again before the timeout clears the previous timer for that callback.
|
|
138
|
+
*
|
|
139
|
+
* Once rules:
|
|
140
|
+
* - If an entry has `once = true`, it is removed after execution by setting its slot to `undefined`.
|
|
141
|
+
* (The list is not spliced to preserve indices.)
|
|
142
|
+
*/
|
|
143
|
+
run(key, ...params) {
|
|
144
|
+
const executes = this.executeStored.get(key);
|
|
145
|
+
if (!executes)
|
|
146
|
+
return;
|
|
147
|
+
if (!this.timerRunner.has(key))
|
|
148
|
+
this.timerRunner.set(key, new Map());
|
|
149
|
+
const runner = this.timerRunner.get(key);
|
|
150
|
+
for (let i = 0; i < executes.length; i++) {
|
|
151
|
+
const entry = executes[i];
|
|
152
|
+
if (!entry)
|
|
153
|
+
continue;
|
|
154
|
+
const prev = runner.get(i);
|
|
155
|
+
if (prev)
|
|
156
|
+
clearTimeout(prev);
|
|
157
|
+
const timer = setTimeout(() => {
|
|
158
|
+
entry.callback(params.length > 0 ? params : null);
|
|
159
|
+
if (entry.once) {
|
|
160
|
+
// Preserve index stability by leaving an empty slot.
|
|
161
|
+
executes[i] = undefined;
|
|
162
|
+
// Cleanup the timer handle for this index.
|
|
163
|
+
const current = runner.get(i);
|
|
164
|
+
if (current)
|
|
165
|
+
clearTimeout(current);
|
|
166
|
+
runner.delete(i);
|
|
167
|
+
}
|
|
168
|
+
}, entry.timeout);
|
|
169
|
+
runner.set(i, timer);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Clears callbacks and timers.
|
|
174
|
+
*
|
|
175
|
+
* @param key - When provided, clears only that key; otherwise clears all keys.
|
|
176
|
+
*/
|
|
177
|
+
clear(key) {
|
|
178
|
+
if (key !== undefined) {
|
|
179
|
+
this.off(key);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Iterate over a snapshot of keys because `off()` mutates the maps.
|
|
183
|
+
for (const k of Array.from(this.executeStored.keys())) {
|
|
184
|
+
this.off(k);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
69
189
|
/**
|
|
70
190
|
* @class
|
|
71
191
|
*/
|
|
@@ -80,18 +200,6 @@ class Libs {
|
|
|
80
200
|
this._iStorage = new iStorage();
|
|
81
201
|
return this._iStorage;
|
|
82
202
|
}
|
|
83
|
-
/**
|
|
84
|
-
* Checks whether a value is null/undefined/empty-string/"0"/0.
|
|
85
|
-
* Booleans are always considered non-empty.
|
|
86
|
-
*
|
|
87
|
-
* @param {unknown} value - The value to test.
|
|
88
|
-
* @returns {boolean} - True if considered empty; otherwise false.
|
|
89
|
-
*/
|
|
90
|
-
static isNullOrEmpty(value) {
|
|
91
|
-
if (typeof value === "boolean")
|
|
92
|
-
return false;
|
|
93
|
-
return value == null || value === "" || value === 0 || value === "0";
|
|
94
|
-
}
|
|
95
203
|
/**
|
|
96
204
|
* Deep-copies plain objects/arrays recursively. Returns primitives as-is.
|
|
97
205
|
*
|
|
@@ -289,23 +397,6 @@ class Libs {
|
|
|
289
397
|
}
|
|
290
398
|
return recursiveTemp;
|
|
291
399
|
}
|
|
292
|
-
/**
|
|
293
|
-
* Applies inline CSS styles to all matched elements. Accepts either a style
|
|
294
|
-
* object or a single property + value pair.
|
|
295
|
-
*
|
|
296
|
-
* @param {string|NodeListOf<HTMLElement>|HTMLElement} queryCommon - Selector or element(s).
|
|
297
|
-
* @param {Record<string, string>|string} styles - Style object or a single property name.
|
|
298
|
-
* @param {string|null} [value=null] - Value for the single property form.
|
|
299
|
-
*/
|
|
300
|
-
static setStyle(queryCommon, styles, value = null) {
|
|
301
|
-
const apply_styles = typeof styles === "string" ? { [styles]: value } : { ...styles };
|
|
302
|
-
const queryItems = this.getElements(queryCommon);
|
|
303
|
-
for (let i = 0; i < queryItems.length; i++) {
|
|
304
|
-
const item = queryItems[i];
|
|
305
|
-
if (item)
|
|
306
|
-
Object.assign(item.style, apply_styles);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
400
|
/**
|
|
310
401
|
* Builds a configuration object by copying defaults and then overriding with
|
|
311
402
|
* matching element properties or data-* attributes when present.
|
|
@@ -590,41 +681,7 @@ Libs._iStorage = null;
|
|
|
590
681
|
* Schedules and batches function executions keyed by name, with debounced timers.
|
|
591
682
|
* Provides setExecute(), clearExecute(), and run() to manage deferred callbacks.
|
|
592
683
|
*/
|
|
593
|
-
Libs.
|
|
594
|
-
executeStored: {},
|
|
595
|
-
timerRunner: {},
|
|
596
|
-
setExecute(keyExecute, execute, timeout = 50, once = false) {
|
|
597
|
-
if (!this.executeStored[keyExecute])
|
|
598
|
-
this.executeStored[keyExecute] = [];
|
|
599
|
-
this.executeStored[keyExecute].push({ execute, timeout, once });
|
|
600
|
-
},
|
|
601
|
-
clearExecute(keyExecute) {
|
|
602
|
-
delete this.executeStored[keyExecute];
|
|
603
|
-
},
|
|
604
|
-
run(keyExecute, ...params) {
|
|
605
|
-
const executes = this.executeStored[keyExecute];
|
|
606
|
-
if (!executes)
|
|
607
|
-
return;
|
|
608
|
-
if (!this.timerRunner[keyExecute])
|
|
609
|
-
this.timerRunner[keyExecute] = {};
|
|
610
|
-
for (const key in executes) {
|
|
611
|
-
const entry = executes[Number(key)];
|
|
612
|
-
if (!entry)
|
|
613
|
-
continue;
|
|
614
|
-
if (!this.timerRunner[keyExecute][key]) {
|
|
615
|
-
// placeholder, will be overwritten by setTimeout
|
|
616
|
-
this.timerRunner[keyExecute][key] = setTimeout(() => { }, 0);
|
|
617
|
-
clearTimeout(this.timerRunner[keyExecute][key]);
|
|
618
|
-
}
|
|
619
|
-
clearTimeout(this.timerRunner[keyExecute][key]);
|
|
620
|
-
this.timerRunner[keyExecute][key] = setTimeout(() => {
|
|
621
|
-
entry.execute(params.length > 0 ? params : null);
|
|
622
|
-
if (entry.once)
|
|
623
|
-
delete this.executeStored[keyExecute][Number(key)];
|
|
624
|
-
}, entry.timeout);
|
|
625
|
-
}
|
|
626
|
-
},
|
|
627
|
-
};
|
|
684
|
+
Libs.callbackScheduler = new CallbackScheduler();
|
|
628
685
|
|
|
629
686
|
/**
|
|
630
687
|
* Provides a lightweight event utility with cancel/continue control:
|
|
@@ -743,7 +800,7 @@ class Refresher {
|
|
|
743
800
|
width = options.width;
|
|
744
801
|
if (cfgHeight > 0)
|
|
745
802
|
height = options.height;
|
|
746
|
-
|
|
803
|
+
Object.assign(view.style, { width, height, minWidth, minHeight });
|
|
747
804
|
}
|
|
748
805
|
}
|
|
749
806
|
|
|
@@ -756,9 +813,6 @@ class PlaceHolder {
|
|
|
756
813
|
* Supports HTML content based on configuration and provides methods to get or set the placeholder value.
|
|
757
814
|
*/
|
|
758
815
|
constructor(options) {
|
|
759
|
-
/**
|
|
760
|
-
* @type {HTMLElement | null}
|
|
761
|
-
*/
|
|
762
816
|
this.node = null;
|
|
763
817
|
this._options = null;
|
|
764
818
|
if (options)
|
|
@@ -843,14 +897,9 @@ class OptionHandle {
|
|
|
843
897
|
*/
|
|
844
898
|
constructor(options = null) {
|
|
845
899
|
this.nodeMounted = null;
|
|
846
|
-
/**
|
|
847
|
-
* @type {HTMLDivElement | null}
|
|
848
|
-
*/
|
|
849
900
|
this.node = null;
|
|
850
901
|
this.options = null;
|
|
851
|
-
/** @type {Function[]} */
|
|
852
902
|
this._ActionOnSelectAll = [];
|
|
853
|
-
/** @type {Function[]} */
|
|
854
903
|
this._ActionOnDeSelectAll = [];
|
|
855
904
|
if (options)
|
|
856
905
|
this.init(options);
|
|
@@ -959,9 +1008,6 @@ class EmptyState {
|
|
|
959
1008
|
* Provides methods to show/hide the state and check its visibility.
|
|
960
1009
|
*/
|
|
961
1010
|
constructor(options = null) {
|
|
962
|
-
/**
|
|
963
|
-
* @type {HTMLDivElement | null}
|
|
964
|
-
*/
|
|
965
1011
|
this.node = null;
|
|
966
1012
|
this.options = null;
|
|
967
1013
|
if (options)
|
|
@@ -1012,13 +1058,15 @@ class EmptyState {
|
|
|
1012
1058
|
}
|
|
1013
1059
|
}
|
|
1014
1060
|
|
|
1061
|
+
/**
|
|
1062
|
+
* @class
|
|
1063
|
+
*/
|
|
1015
1064
|
class LoadingState {
|
|
1016
1065
|
/**
|
|
1017
1066
|
* Represents a loading state component that displays a loading message during data fetch or processing.
|
|
1018
1067
|
* Provides methods to show/hide the state and check its visibility.
|
|
1019
1068
|
*/
|
|
1020
1069
|
constructor(options = null) {
|
|
1021
|
-
/** @type {HTMLDivElement | null} */
|
|
1022
1070
|
this.node = null;
|
|
1023
1071
|
this.options = null;
|
|
1024
1072
|
if (options)
|
|
@@ -1080,7 +1128,6 @@ class ResizeObserverService {
|
|
|
1080
1128
|
constructor() {
|
|
1081
1129
|
this.isInit = false;
|
|
1082
1130
|
this.element = null;
|
|
1083
|
-
/** @type {ResizeObserver|null} */
|
|
1084
1131
|
this._resizeObserver = null;
|
|
1085
1132
|
this._mutationObserver = null;
|
|
1086
1133
|
this.isInit = true;
|
|
@@ -1210,27 +1257,17 @@ class Popup {
|
|
|
1210
1257
|
constructor(select = null, options = null, modelManager = null) {
|
|
1211
1258
|
this.options = null;
|
|
1212
1259
|
this.isCreated = false;
|
|
1213
|
-
/** @type {MixedAdapter | null} */
|
|
1214
1260
|
this.optionAdapter = null;
|
|
1215
|
-
/** @type {HTMLDivElement | null} */
|
|
1216
1261
|
this.node = null;
|
|
1217
|
-
/** @type {EffectorInterface | null} */
|
|
1218
1262
|
this._effSvc = null;
|
|
1219
|
-
/** @type {ResizeObserverService | null} */
|
|
1220
1263
|
this._resizeObser = null;
|
|
1221
1264
|
this._parent = null;
|
|
1222
|
-
/** @type {OptionHandle | null} */
|
|
1223
1265
|
this.optionHandle = null;
|
|
1224
|
-
/** @type {EmptyState | null} */
|
|
1225
1266
|
this.emptyState = null;
|
|
1226
|
-
/** @type {LoadingState | null} */
|
|
1227
1267
|
this.loadingState = null;
|
|
1228
|
-
/** @type {RecyclerViewContract<MixedAdapter> | null} */
|
|
1229
1268
|
this.recyclerView = null;
|
|
1230
|
-
/** @type {HTMLDivElement | null} */
|
|
1231
1269
|
this._optionsContainer = null;
|
|
1232
1270
|
this._scrollListener = null;
|
|
1233
|
-
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
1234
1271
|
this._hideLoadHandle = null;
|
|
1235
1272
|
this._modelManager = modelManager;
|
|
1236
1273
|
if (select && options) {
|
|
@@ -1449,6 +1486,9 @@ class Popup {
|
|
|
1449
1486
|
/**
|
|
1450
1487
|
* Enables infinite scroll by listening to container scroll events and loading more data
|
|
1451
1488
|
* when nearing the bottom, respecting pagination state (enabled/loading/hasMore).
|
|
1489
|
+
*
|
|
1490
|
+
* @param searchController - Provides pagination state and a method to load more items.
|
|
1491
|
+
* @param _options - Optional SelectiveOptions (reserved for future behavior tuning).
|
|
1452
1492
|
*/
|
|
1453
1493
|
setupInfiniteScroll(searchController, _options) {
|
|
1454
1494
|
if (!this.node)
|
|
@@ -1473,6 +1513,65 @@ class Popup {
|
|
|
1473
1513
|
};
|
|
1474
1514
|
this.node.addEventListener("scroll", this._scrollListener);
|
|
1475
1515
|
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Completely tear down the popup instance and release all resources.
|
|
1518
|
+
*
|
|
1519
|
+
* Responsibilities:
|
|
1520
|
+
* - Clear any pending timeouts and cancel animations/effects.
|
|
1521
|
+
* - Remove event listeners (scroll, mousedown) and disconnect ResizeObserver.
|
|
1522
|
+
* - Unmount and remove the DOM node; sever references to Effector/ModelManager.
|
|
1523
|
+
* - Dispose adapter/recycler and child components (OptionHandle, EmptyState, LoadingState).
|
|
1524
|
+
* - Reset flags and null out references to avoid memory leaks.
|
|
1525
|
+
*
|
|
1526
|
+
* Safe to call multiple times; all operations are guarded via optional chaining.
|
|
1527
|
+
*/
|
|
1528
|
+
detroy() {
|
|
1529
|
+
if (this._hideLoadHandle) {
|
|
1530
|
+
clearTimeout(this._hideLoadHandle);
|
|
1531
|
+
this._hideLoadHandle = null;
|
|
1532
|
+
}
|
|
1533
|
+
if (this.node && this._scrollListener) {
|
|
1534
|
+
this.node.removeEventListener("scroll", this._scrollListener);
|
|
1535
|
+
this._scrollListener = null;
|
|
1536
|
+
}
|
|
1537
|
+
try {
|
|
1538
|
+
this._resizeObser?.disconnect();
|
|
1539
|
+
}
|
|
1540
|
+
catch (_) { }
|
|
1541
|
+
this._resizeObser = null;
|
|
1542
|
+
try {
|
|
1543
|
+
this._effSvc?.setElement?.(null);
|
|
1544
|
+
}
|
|
1545
|
+
catch (_) { }
|
|
1546
|
+
this._effSvc = null;
|
|
1547
|
+
if (this.node) {
|
|
1548
|
+
try {
|
|
1549
|
+
const clone = this.node.cloneNode(true);
|
|
1550
|
+
this.node.replaceWith(clone);
|
|
1551
|
+
clone.remove();
|
|
1552
|
+
}
|
|
1553
|
+
catch (_) {
|
|
1554
|
+
this.node.remove();
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
this.node = null;
|
|
1558
|
+
this._optionsContainer = null;
|
|
1559
|
+
try {
|
|
1560
|
+
this._modelManager?.skipEvent?.(false);
|
|
1561
|
+
this.recyclerView?.clear?.();
|
|
1562
|
+
this.recyclerView = null;
|
|
1563
|
+
this.optionAdapter = null;
|
|
1564
|
+
this.node.remove();
|
|
1565
|
+
}
|
|
1566
|
+
catch (_) { }
|
|
1567
|
+
this._modelManager = null;
|
|
1568
|
+
this.optionHandle = null;
|
|
1569
|
+
this.emptyState = null;
|
|
1570
|
+
this.loadingState = null;
|
|
1571
|
+
this._parent = null;
|
|
1572
|
+
this.options = null;
|
|
1573
|
+
this.isCreated = false;
|
|
1574
|
+
}
|
|
1476
1575
|
/**
|
|
1477
1576
|
* Computes the parent panel's location and box metrics, including size, position,
|
|
1478
1577
|
* padding, and border, accounting for iOS visual viewport offsets.
|
|
@@ -1578,21 +1677,9 @@ class SearchBox {
|
|
|
1578
1677
|
* @param {object|null} [options=null] - Configuration (e.g., placeholder, accessibility IDs).
|
|
1579
1678
|
*/
|
|
1580
1679
|
constructor(options = null) {
|
|
1581
|
-
/**
|
|
1582
|
-
* @type {MountViewResult<any> | null}
|
|
1583
|
-
*/
|
|
1584
1680
|
this.nodeMounted = null;
|
|
1585
|
-
/**
|
|
1586
|
-
* @type {HTMLDivElement | null}
|
|
1587
|
-
*/
|
|
1588
1681
|
this.node = null;
|
|
1589
|
-
/**
|
|
1590
|
-
* @type {HTMLInputElement | null}
|
|
1591
|
-
*/
|
|
1592
1682
|
this.SearchInput = null;
|
|
1593
|
-
/**
|
|
1594
|
-
* @type {Function|null}
|
|
1595
|
-
*/
|
|
1596
1683
|
this.onSearch = null;
|
|
1597
1684
|
this.options = null;
|
|
1598
1685
|
this.onNavigate = null;
|
|
@@ -1999,7 +2086,9 @@ class EffectorImpl {
|
|
|
1999
2086
|
}
|
|
2000
2087
|
else {
|
|
2001
2088
|
this._resizeTimeout = setTimeout(() => {
|
|
2002
|
-
this.element
|
|
2089
|
+
if (this.element?.style) {
|
|
2090
|
+
this.element.style.transition = "none";
|
|
2091
|
+
}
|
|
2003
2092
|
}, duration);
|
|
2004
2093
|
}
|
|
2005
2094
|
Object.assign(this.element.style, styles);
|
|
@@ -2053,7 +2142,6 @@ class Model {
|
|
|
2053
2142
|
constructor(options, targetElement = null, view = null) {
|
|
2054
2143
|
/** @type {TTarget | null} */
|
|
2055
2144
|
this.targetElement = null;
|
|
2056
|
-
/** @type {TView | null} */
|
|
2057
2145
|
this.view = null;
|
|
2058
2146
|
this.position = -1;
|
|
2059
2147
|
this.isInit = false;
|
|
@@ -2091,7 +2179,6 @@ class GroupModel extends Model {
|
|
|
2091
2179
|
constructor(options, targetElement) {
|
|
2092
2180
|
super(options, targetElement ?? null, null);
|
|
2093
2181
|
this.label = "";
|
|
2094
|
-
/** @type {OptionModel[]} */
|
|
2095
2182
|
this.items = [];
|
|
2096
2183
|
this.collapsed = false;
|
|
2097
2184
|
this._privOnCollapsedChanged = [];
|
|
@@ -2289,7 +2376,7 @@ class OptionModel extends Model {
|
|
|
2289
2376
|
* @type {boolean}
|
|
2290
2377
|
*/
|
|
2291
2378
|
set selectedNonTrigger(value) {
|
|
2292
|
-
const input = this.view?.
|
|
2379
|
+
const input = this.view?.view?.tags?.OptionInput;
|
|
2293
2380
|
const viewEl = this.view?.getView?.();
|
|
2294
2381
|
if (input)
|
|
2295
2382
|
input.checked = value;
|
|
@@ -2384,7 +2471,7 @@ class OptionModel extends Model {
|
|
|
2384
2471
|
onTargetChanged() {
|
|
2385
2472
|
if (!this.view)
|
|
2386
2473
|
return;
|
|
2387
|
-
const labelContent = this.view.
|
|
2474
|
+
const labelContent = this.view.view.tags.LabelContent;
|
|
2388
2475
|
if (labelContent) {
|
|
2389
2476
|
if (this.options.allowHtml) {
|
|
2390
2477
|
labelContent.innerHTML = this.text;
|
|
@@ -2393,7 +2480,7 @@ class OptionModel extends Model {
|
|
|
2393
2480
|
labelContent.textContent = this.textContent;
|
|
2394
2481
|
}
|
|
2395
2482
|
}
|
|
2396
|
-
const imageTag = this.view.
|
|
2483
|
+
const imageTag = this.view.view.tags.OptionImage;
|
|
2397
2484
|
if (imageTag && this.hasImage) {
|
|
2398
2485
|
imageTag.src = this.imageSrc;
|
|
2399
2486
|
imageTag.alt = this.text;
|
|
@@ -2570,7 +2657,6 @@ class ModelManager {
|
|
|
2570
2657
|
});
|
|
2571
2658
|
let currentGroup = null;
|
|
2572
2659
|
let position = 0;
|
|
2573
|
-
const changesToApply = [];
|
|
2574
2660
|
modelData.forEach((data) => {
|
|
2575
2661
|
if (data.tagName === "OPTGROUP") {
|
|
2576
2662
|
const dataVset = data;
|
|
@@ -2579,7 +2665,7 @@ class ModelManager {
|
|
|
2579
2665
|
// Label is used as key; keep original behavior.
|
|
2580
2666
|
const hasLabelChange = existingGroup.label !== dataVset.label;
|
|
2581
2667
|
if (hasLabelChange) {
|
|
2582
|
-
|
|
2668
|
+
existingGroup.update(dataVset);
|
|
2583
2669
|
}
|
|
2584
2670
|
existingGroup.position = position;
|
|
2585
2671
|
existingGroup.items = [];
|
|
@@ -2599,17 +2685,8 @@ class ModelManager {
|
|
|
2599
2685
|
const key = `${dataVset.value}::${dataVset.text}`;
|
|
2600
2686
|
const existingOption = oldOptionMap.get(key);
|
|
2601
2687
|
if (existingOption) {
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
if (hasSelectedChange || hasPositionChange) {
|
|
2605
|
-
changesToApply.push(() => {
|
|
2606
|
-
existingOption.update(dataVset);
|
|
2607
|
-
existingOption.position = position;
|
|
2608
|
-
});
|
|
2609
|
-
}
|
|
2610
|
-
else {
|
|
2611
|
-
existingOption.position = position;
|
|
2612
|
-
}
|
|
2688
|
+
existingOption.update(dataVset);
|
|
2689
|
+
existingOption.position = position;
|
|
2613
2690
|
const parentGroup = dataVset["__parentGroup"];
|
|
2614
2691
|
if (parentGroup && currentGroup) {
|
|
2615
2692
|
currentGroup.addItem(existingOption);
|
|
@@ -2636,11 +2713,6 @@ class ModelManager {
|
|
|
2636
2713
|
position++;
|
|
2637
2714
|
}
|
|
2638
2715
|
});
|
|
2639
|
-
if (changesToApply.length > 0) {
|
|
2640
|
-
requestAnimationFrame(() => {
|
|
2641
|
-
changesToApply.forEach((change) => change());
|
|
2642
|
-
});
|
|
2643
|
-
}
|
|
2644
2716
|
oldGroupMap.forEach((removedGroup) => {
|
|
2645
2717
|
removedGroup.view?.getView?.()?.remove?.();
|
|
2646
2718
|
});
|
|
@@ -2683,9 +2755,6 @@ class ModelManager {
|
|
|
2683
2755
|
* adapter instance, and recycler view instance.
|
|
2684
2756
|
*/
|
|
2685
2757
|
getResources() {
|
|
2686
|
-
if (!this._privAdapterHandle || !this._privRecyclerViewHandle) {
|
|
2687
|
-
throw new Error("ModelManager resources not loaded. Call load() first.");
|
|
2688
|
-
}
|
|
2689
2758
|
return {
|
|
2690
2759
|
modelList: this._privModelList,
|
|
2691
2760
|
adapter: this._privAdapterHandle,
|
|
@@ -2720,13 +2789,7 @@ class RecyclerView {
|
|
|
2720
2789
|
* @param {HTMLDivElement|null} [viewElement=null] - The root element where the adapter will render items.
|
|
2721
2790
|
*/
|
|
2722
2791
|
constructor(viewElement = null) {
|
|
2723
|
-
/**
|
|
2724
|
-
* @type {HTMLDivElement|null}
|
|
2725
|
-
*/
|
|
2726
2792
|
this.viewElement = null;
|
|
2727
|
-
/**
|
|
2728
|
-
* @type {TAdapter|null}
|
|
2729
|
-
*/
|
|
2730
2793
|
this.adapter = null;
|
|
2731
2794
|
this.viewElement = viewElement;
|
|
2732
2795
|
}
|
|
@@ -2793,20 +2856,11 @@ class AccessoryBox {
|
|
|
2793
2856
|
* @param {object|null} options - Configuration options for the accessory box (e.g., layout and behavior).
|
|
2794
2857
|
*/
|
|
2795
2858
|
constructor(options = null) {
|
|
2796
|
-
/**
|
|
2797
|
-
* @type {MountViewResult<any> | null}
|
|
2798
|
-
*/
|
|
2799
2859
|
this.nodeMounted = null;
|
|
2800
|
-
/**
|
|
2801
|
-
* @type {HTMLDivElement | null}
|
|
2802
|
-
*/
|
|
2803
2860
|
this.node = null;
|
|
2804
2861
|
this.options = null;
|
|
2805
|
-
/** @type {HTMLDivElement | null} */
|
|
2806
2862
|
this.selectUIMask = null;
|
|
2807
|
-
/** @type {HTMLDivElement | null} */
|
|
2808
2863
|
this.parentMask = null;
|
|
2809
|
-
/** @type {ModelManager<MixedItem> | null} */
|
|
2810
2864
|
this.modelManager = null;
|
|
2811
2865
|
if (options)
|
|
2812
2866
|
this.init(options);
|
|
@@ -3507,7 +3561,6 @@ class Adapter {
|
|
|
3507
3561
|
* @param {TItem[]} [items=[]] - Initial items to be managed by the adapter.
|
|
3508
3562
|
*/
|
|
3509
3563
|
constructor(items = []) {
|
|
3510
|
-
/** @type {TItem[]} */
|
|
3511
3564
|
this.items = [];
|
|
3512
3565
|
this.adapterKey = Libs.randomString(12);
|
|
3513
3566
|
this.isSkipEvent = false;
|
|
@@ -3544,7 +3597,7 @@ class Adapter {
|
|
|
3544
3597
|
* @param {Function} callback - Function to execute before the property changes.
|
|
3545
3598
|
*/
|
|
3546
3599
|
onPropChanging(propName, callback) {
|
|
3547
|
-
Libs.
|
|
3600
|
+
Libs.callbackScheduler.on(`${propName}ing_${this.adapterKey}`, callback, { debounce: 1 });
|
|
3548
3601
|
}
|
|
3549
3602
|
/**
|
|
3550
3603
|
* Registers a post-change callback for a property change pipeline.
|
|
@@ -3554,7 +3607,7 @@ class Adapter {
|
|
|
3554
3607
|
* @param {Function} callback - Function to execute after the property changes.
|
|
3555
3608
|
*/
|
|
3556
3609
|
onPropChanged(propName, callback) {
|
|
3557
|
-
Libs.
|
|
3610
|
+
Libs.callbackScheduler.on(`${propName}_${this.adapterKey}`, callback);
|
|
3558
3611
|
}
|
|
3559
3612
|
/**
|
|
3560
3613
|
* Triggers the post-change pipeline for a given property, passing optional parameters
|
|
@@ -3564,7 +3617,7 @@ class Adapter {
|
|
|
3564
3617
|
* @param {...any} params - Parameters forwarded to the callbacks.
|
|
3565
3618
|
*/
|
|
3566
3619
|
changeProp(propName, ...params) {
|
|
3567
|
-
Libs.
|
|
3620
|
+
Libs.callbackScheduler.run(`${propName}_${this.adapterKey}`, ...params);
|
|
3568
3621
|
}
|
|
3569
3622
|
/**
|
|
3570
3623
|
* Triggers the pre-change pipeline for a given property, passing optional parameters
|
|
@@ -3574,7 +3627,7 @@ class Adapter {
|
|
|
3574
3627
|
* @param {...any} params - Parameters forwarded to the callbacks.
|
|
3575
3628
|
*/
|
|
3576
3629
|
changingProp(propName, ...params) {
|
|
3577
|
-
Libs.
|
|
3630
|
+
Libs.callbackScheduler.run(`${propName}ing_${this.adapterKey}`, ...params);
|
|
3578
3631
|
}
|
|
3579
3632
|
/**
|
|
3580
3633
|
* Creates and returns a viewer instance for the given item within the specified parent container.
|
|
@@ -3654,9 +3707,7 @@ class View {
|
|
|
3654
3707
|
* @param {HTMLElement} parent - The parent element into which this view will render.
|
|
3655
3708
|
*/
|
|
3656
3709
|
constructor(parent) {
|
|
3657
|
-
/** @type {HTMLElement|null} */
|
|
3658
3710
|
this.parent = null;
|
|
3659
|
-
/** @type {MountViewResult<TTags> | null} */
|
|
3660
3711
|
this.view = null;
|
|
3661
3712
|
this.parent = parent;
|
|
3662
3713
|
}
|
|
@@ -3680,28 +3731,6 @@ class View {
|
|
|
3680
3731
|
throw new Error("View is not mounted. Did you forget to set this.view?");
|
|
3681
3732
|
return this.view.view;
|
|
3682
3733
|
}
|
|
3683
|
-
/**
|
|
3684
|
-
* Retrieves a single tagged element from the mounted view.
|
|
3685
|
-
*
|
|
3686
|
-
* @template K
|
|
3687
|
-
* @param {K} tag - The tag key corresponding to the desired element.
|
|
3688
|
-
* @returns {TTags[K]} - The element associated with the provided tag key.
|
|
3689
|
-
*/
|
|
3690
|
-
getTag(tag) {
|
|
3691
|
-
if (!this.view)
|
|
3692
|
-
throw new Error("View is not mounted. Did you forget to set this.view?");
|
|
3693
|
-
return this.view.tags[tag];
|
|
3694
|
-
}
|
|
3695
|
-
/**
|
|
3696
|
-
* Retrieves the full tag map for the mounted view.
|
|
3697
|
-
*
|
|
3698
|
-
* @returns {TTags} - An object map of all tagged elements.
|
|
3699
|
-
*/
|
|
3700
|
-
getTags() {
|
|
3701
|
-
if (!this.view)
|
|
3702
|
-
throw new Error("View is not mounted. Did you forget to set this.view?");
|
|
3703
|
-
return this.view.tags;
|
|
3704
|
-
}
|
|
3705
3734
|
}
|
|
3706
3735
|
|
|
3707
3736
|
/**
|
|
@@ -3710,7 +3739,6 @@ class View {
|
|
|
3710
3739
|
class GroupView extends View {
|
|
3711
3740
|
constructor() {
|
|
3712
3741
|
super(...arguments);
|
|
3713
|
-
/** @type {GroupViewResult} */
|
|
3714
3742
|
this.view = null;
|
|
3715
3743
|
}
|
|
3716
3744
|
/**
|
|
@@ -4158,7 +4186,7 @@ class MixedAdapter extends Adapter {
|
|
|
4158
4186
|
_handleGroupView(groupModel, groupView, position) {
|
|
4159
4187
|
super.onViewHolder(groupModel, groupView, position);
|
|
4160
4188
|
groupModel.view = groupView;
|
|
4161
|
-
const header = groupView.
|
|
4189
|
+
const header = groupView.view.tags.GroupHeader;
|
|
4162
4190
|
header.textContent = groupModel.label;
|
|
4163
4191
|
if (!groupModel.isInit) {
|
|
4164
4192
|
header.style.cursor = "pointer";
|
|
@@ -4210,7 +4238,7 @@ class MixedAdapter extends Adapter {
|
|
|
4210
4238
|
}
|
|
4211
4239
|
optionModel.view = optionViewer;
|
|
4212
4240
|
if (optionModel.hasImage) {
|
|
4213
|
-
const imageTag = optionViewer.
|
|
4241
|
+
const imageTag = optionViewer.view.tags.OptionImage;
|
|
4214
4242
|
if (imageTag) {
|
|
4215
4243
|
if (imageTag.src !== optionModel.imageSrc)
|
|
4216
4244
|
imageTag.src = optionModel.imageSrc;
|
|
@@ -4218,9 +4246,9 @@ class MixedAdapter extends Adapter {
|
|
|
4218
4246
|
imageTag.alt = optionModel.text;
|
|
4219
4247
|
}
|
|
4220
4248
|
}
|
|
4221
|
-
optionViewer.
|
|
4249
|
+
optionViewer.view.tags.LabelContent.innerHTML = optionModel.text;
|
|
4222
4250
|
if (!optionModel.isInit) {
|
|
4223
|
-
optionViewer.
|
|
4251
|
+
optionViewer.view.tags.OptionView.addEventListener("click", (ev) => {
|
|
4224
4252
|
ev.stopPropagation();
|
|
4225
4253
|
ev.preventDefault();
|
|
4226
4254
|
if (this.isSkipEvent)
|
|
@@ -4240,8 +4268,8 @@ class MixedAdapter extends Adapter {
|
|
|
4240
4268
|
}, 5);
|
|
4241
4269
|
}
|
|
4242
4270
|
});
|
|
4243
|
-
optionViewer.
|
|
4244
|
-
optionViewer.
|
|
4271
|
+
optionViewer.view.tags.OptionView.title = optionModel.textContent;
|
|
4272
|
+
optionViewer.view.tags.OptionView.addEventListener("mouseenter", () => {
|
|
4245
4273
|
if (this.isSkipEvent)
|
|
4246
4274
|
return;
|
|
4247
4275
|
this.setHighlight(this.flatOptions.indexOf(optionModel), false);
|
|
@@ -4464,10 +4492,8 @@ class SelectBox {
|
|
|
4464
4492
|
constructor(select = null, Selective = null) {
|
|
4465
4493
|
this.container = {};
|
|
4466
4494
|
this.oldValue = null;
|
|
4467
|
-
/** @type {HTMLDivElement|null} */
|
|
4468
4495
|
this.node = null;
|
|
4469
4496
|
this.options = null;
|
|
4470
|
-
/** @type {ModelManager<MixedItem, MixedAdapter> | null} */
|
|
4471
4497
|
this.optionModelManager = null;
|
|
4472
4498
|
this.isOpen = false;
|
|
4473
4499
|
this.hasLoadedOnce = false;
|
|
@@ -5044,6 +5070,9 @@ class SelectBox {
|
|
|
5044
5070
|
}
|
|
5045
5071
|
return flatOptions;
|
|
5046
5072
|
}
|
|
5073
|
+
detroy() {
|
|
5074
|
+
this.container.popup.detroy();
|
|
5075
|
+
}
|
|
5047
5076
|
}
|
|
5048
5077
|
|
|
5049
5078
|
/**
|
|
@@ -5052,9 +5081,7 @@ class SelectBox {
|
|
|
5052
5081
|
class ElementAdditionObserver {
|
|
5053
5082
|
constructor() {
|
|
5054
5083
|
this._isActive = false;
|
|
5055
|
-
/** @type {MutationObserver|null} */
|
|
5056
5084
|
this._observer = null;
|
|
5057
|
-
/** @type {Array<(el: T) => void>} */
|
|
5058
5085
|
this._actions = [];
|
|
5059
5086
|
}
|
|
5060
5087
|
/**
|
|
@@ -5139,9 +5166,9 @@ class Selective {
|
|
|
5139
5166
|
merged.on.load = (merged.on.load ?? []);
|
|
5140
5167
|
this.bindedQueries.set(query, merged);
|
|
5141
5168
|
const doneToken = Libs.randomString();
|
|
5142
|
-
Libs.
|
|
5169
|
+
Libs.callbackScheduler.on(doneToken, () => {
|
|
5143
5170
|
iEvents.callEvent([this.find(query)], ...merged.on.load);
|
|
5144
|
-
Libs.
|
|
5171
|
+
Libs.callbackScheduler.clear(doneToken);
|
|
5145
5172
|
merged.on.load = [];
|
|
5146
5173
|
});
|
|
5147
5174
|
const selectElements = Libs.getElements(query);
|
|
@@ -5150,7 +5177,7 @@ class Selective {
|
|
|
5150
5177
|
if (item.tagName === "SELECT") {
|
|
5151
5178
|
Libs.removeUnbinderMap(item);
|
|
5152
5179
|
if (this.applySelectBox(item, merged)) {
|
|
5153
|
-
Libs.
|
|
5180
|
+
Libs.callbackScheduler.run(doneToken);
|
|
5154
5181
|
}
|
|
5155
5182
|
}
|
|
5156
5183
|
})();
|
|
@@ -5275,6 +5302,8 @@ class Selective {
|
|
|
5275
5302
|
const bindMap = Libs.getBinderMap(selectElement);
|
|
5276
5303
|
if (!bindMap)
|
|
5277
5304
|
return;
|
|
5305
|
+
const popup = bindMap.container?.popup;
|
|
5306
|
+
popup?.detroy();
|
|
5278
5307
|
Libs.setUnbinderMap(selectElement, bindMap);
|
|
5279
5308
|
const wasObserving = !!this.EAObserver;
|
|
5280
5309
|
if (wasObserving)
|
|
@@ -5415,7 +5444,6 @@ Selective.bindedQueries = new Map();
|
|
|
5415
5444
|
|
|
5416
5445
|
/**
|
|
5417
5446
|
* Checks for a previously loaded global library instance by name.
|
|
5418
|
-
* If found (with `__loaded` flag), logs a warning and returns true; otherwise
|
|
5419
5447
|
* initializes a loading placeholder on `window[name]` and returns false.
|
|
5420
5448
|
*
|
|
5421
5449
|
* @param {string} LIB_NAME - The global namespace key to check on `window`.
|
|
@@ -5425,13 +5453,12 @@ function checkDuplicate(LIB_NAME) {
|
|
|
5425
5453
|
if (typeof window === "undefined")
|
|
5426
5454
|
return false;
|
|
5427
5455
|
const existing = window[LIB_NAME];
|
|
5428
|
-
if (existing
|
|
5429
|
-
console.warn(`[${LIB_NAME}] Already loaded (v${existing.
|
|
5456
|
+
if (existing) {
|
|
5457
|
+
console.warn(`[${LIB_NAME}] Already loaded (v${existing.version}). ` +
|
|
5430
5458
|
`Using existing instance. Please remove duplicate <script> tags.`);
|
|
5431
5459
|
return true;
|
|
5432
5460
|
}
|
|
5433
5461
|
const base = existing ?? {};
|
|
5434
|
-
base.__loading = true;
|
|
5435
5462
|
window[LIB_NAME] = base;
|
|
5436
5463
|
return false;
|
|
5437
5464
|
}
|
|
@@ -5449,16 +5476,14 @@ function markLoaded(name, version, api) {
|
|
|
5449
5476
|
if (typeof window === "undefined")
|
|
5450
5477
|
return;
|
|
5451
5478
|
const ns = (window[name] ?? {});
|
|
5452
|
-
ns.
|
|
5453
|
-
ns.__loading = false;
|
|
5454
|
-
ns.__version = version;
|
|
5479
|
+
ns.version = version;
|
|
5455
5480
|
Object.assign(ns, api);
|
|
5456
5481
|
Object.freeze(ns);
|
|
5457
5482
|
window[name] = ns;
|
|
5458
5483
|
console.log(`[${name}] v${version} loaded successfully`);
|
|
5459
5484
|
}
|
|
5460
5485
|
|
|
5461
|
-
const version = "1.1.
|
|
5486
|
+
const version = "1.1.2";
|
|
5462
5487
|
const name = "SelectiveUI";
|
|
5463
5488
|
const alreadyLoaded = checkDuplicate(name);
|
|
5464
5489
|
function getGlobal() {
|
|
@@ -5490,7 +5515,7 @@ function find(query) {
|
|
|
5490
5515
|
* Destroys Selective instances associated with the given query.
|
|
5491
5516
|
* Proxies to a global loaded instance if available; otherwise uses local Selective.destroy.
|
|
5492
5517
|
*/
|
|
5493
|
-
function destroy(query) {
|
|
5518
|
+
function destroy(query = null) {
|
|
5494
5519
|
const global = getGlobal();
|
|
5495
5520
|
if (alreadyLoaded && global)
|
|
5496
5521
|
return global.destroy(query);
|
|
@@ -5517,11 +5542,13 @@ function effector(element) {
|
|
|
5517
5542
|
return Effector(element);
|
|
5518
5543
|
}
|
|
5519
5544
|
if (!alreadyLoaded) {
|
|
5520
|
-
|
|
5545
|
+
const api = { bind, find, destroy, rebind, effector, version };
|
|
5546
|
+
markLoaded(name, version, api);
|
|
5547
|
+
let domInitialized = false;
|
|
5521
5548
|
function init() {
|
|
5522
|
-
if (
|
|
5549
|
+
if (domInitialized)
|
|
5523
5550
|
return;
|
|
5524
|
-
|
|
5551
|
+
domInitialized = true;
|
|
5525
5552
|
document.addEventListener("mousedown", () => {
|
|
5526
5553
|
const sels = Libs.getBindedCommand();
|
|
5527
5554
|
if (sels.length > 0) {
|
|
@@ -5531,8 +5558,6 @@ if (!alreadyLoaded) {
|
|
|
5531
5558
|
}
|
|
5532
5559
|
});
|
|
5533
5560
|
Selective.Observer();
|
|
5534
|
-
const api = { bind, find, destroy, rebind, effector, version };
|
|
5535
|
-
markLoaded(name, version, api);
|
|
5536
5561
|
}
|
|
5537
5562
|
if (document.readyState === "loading") {
|
|
5538
5563
|
document.addEventListener("DOMContentLoaded", init);
|