selective-ui 1.2.1 → 1.2.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.css +3 -1
- package/dist/selective-ui.css.map +1 -1
- package/dist/selective-ui.esm.js +435 -405
- package/dist/selective-ui.esm.js.map +1 -1
- package/dist/selective-ui.esm.min.js +2 -2
- package/dist/selective-ui.esm.min.js.br +0 -0
- package/dist/selective-ui.min.css +1 -1
- package/dist/selective-ui.min.css.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 +436 -406
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/css/components/popup.css +3 -1
- package/src/ts/adapter/mixed-adapter.ts +40 -40
- package/src/ts/components/accessorybox.ts +49 -21
- package/src/ts/components/directive.ts +3 -3
- package/src/ts/components/empty-state.ts +7 -7
- package/src/ts/components/loading-state.ts +7 -7
- package/src/ts/components/option-handle.ts +17 -17
- package/src/ts/components/placeholder.ts +12 -12
- package/src/ts/components/popup.ts +93 -108
- package/src/ts/components/searchbox.ts +14 -14
- package/src/ts/components/selectbox.ts +25 -22
- package/src/ts/core/base/adapter.ts +12 -12
- package/src/ts/core/base/model.ts +12 -13
- package/src/ts/core/base/recyclerview.ts +7 -7
- package/src/ts/core/base/view.ts +6 -6
- package/src/ts/core/base/virtual-recyclerview.ts +50 -50
- package/src/ts/core/model-manager.ts +53 -53
- package/src/ts/core/search-controller.ts +69 -69
- package/src/ts/models/group-model.ts +21 -21
- package/src/ts/models/option-model.ts +30 -30
- package/src/ts/services/dataset-observer.ts +17 -17
- package/src/ts/services/ea-observer.ts +21 -21
- package/src/ts/services/effector.ts +25 -25
- package/src/ts/services/refresher.ts +1 -1
- package/src/ts/services/resize-observer.ts +29 -29
- package/src/ts/services/select-observer.ts +29 -29
- package/src/ts/types/components/popup.type.ts +15 -0
- package/src/ts/types/utils/istorage.type.ts +1 -1
- package/src/ts/utils/callback-scheduler.ts +4 -4
- package/src/ts/utils/ievents.ts +4 -4
- package/src/ts/utils/istorage.ts +5 -5
- package/src/ts/utils/libs.ts +38 -29
- package/src/ts/utils/selective.ts +11 -11
- package/src/ts/views/group-view.ts +7 -7
- package/src/ts/views/option-view.ts +51 -51
package/dist/selective-ui.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Selective UI v1.2.
|
|
1
|
+
/*! Selective UI v1.2.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) :
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
class iStorage {
|
|
12
12
|
constructor() {
|
|
13
13
|
this.defaultConfig = {
|
|
14
|
-
|
|
14
|
+
accessoryVisible: true,
|
|
15
15
|
virtualScroll: true,
|
|
16
16
|
accessoryStyle: "top",
|
|
17
17
|
multiple: false,
|
|
@@ -417,10 +417,20 @@
|
|
|
417
417
|
for (const optionKey in myOptions) {
|
|
418
418
|
const propValue = element[optionKey];
|
|
419
419
|
if (propValue) {
|
|
420
|
-
myOptions[optionKey]
|
|
420
|
+
if (typeof myOptions[optionKey] === "boolean") {
|
|
421
|
+
myOptions[optionKey] = this.string2Boolean(propValue);
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
myOptions[optionKey] = propValue;
|
|
425
|
+
}
|
|
421
426
|
}
|
|
422
427
|
else if (typeof element?.dataset?.[optionKey] !== "undefined") {
|
|
423
|
-
myOptions[optionKey]
|
|
428
|
+
if (typeof myOptions[optionKey] === "boolean") {
|
|
429
|
+
myOptions[optionKey] = this.string2Boolean(element.dataset[optionKey]);
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
myOptions[optionKey] = element.dataset[optionKey];
|
|
433
|
+
}
|
|
424
434
|
}
|
|
425
435
|
}
|
|
426
436
|
return myOptions;
|
|
@@ -821,7 +831,7 @@
|
|
|
821
831
|
*/
|
|
822
832
|
constructor(options) {
|
|
823
833
|
this.node = null;
|
|
824
|
-
this.
|
|
834
|
+
this.options = null;
|
|
825
835
|
if (options)
|
|
826
836
|
this.init(options);
|
|
827
837
|
}
|
|
@@ -836,7 +846,7 @@
|
|
|
836
846
|
classList: "selective-ui-placeholder",
|
|
837
847
|
innerHTML: options.placeholder,
|
|
838
848
|
});
|
|
839
|
-
this.
|
|
849
|
+
this.options = options;
|
|
840
850
|
}
|
|
841
851
|
/**
|
|
842
852
|
* Retrieves the current placeholder text from the configuration.
|
|
@@ -844,7 +854,7 @@
|
|
|
844
854
|
* @returns {string} - The current placeholder text.
|
|
845
855
|
*/
|
|
846
856
|
get() {
|
|
847
|
-
return this.
|
|
857
|
+
return this.options?.placeholder ?? "";
|
|
848
858
|
}
|
|
849
859
|
/**
|
|
850
860
|
* Updates the placeholder text and optionally saves it to the configuration.
|
|
@@ -854,12 +864,12 @@
|
|
|
854
864
|
* @param {boolean} [isSave=true] - Whether to persist the new value in the configuration.
|
|
855
865
|
*/
|
|
856
866
|
set(value, isSave = true) {
|
|
857
|
-
if (!this.node || !this.
|
|
867
|
+
if (!this.node || !this.options)
|
|
858
868
|
return;
|
|
859
869
|
if (isSave)
|
|
860
|
-
this.
|
|
870
|
+
this.options.placeholder = value;
|
|
861
871
|
const translated = Libs.tagTranslate(value);
|
|
862
|
-
this.node.innerHTML = this.
|
|
872
|
+
this.node.innerHTML = this.options.allowHtml ? translated : Libs.stripHtml(translated);
|
|
863
873
|
}
|
|
864
874
|
}
|
|
865
875
|
|
|
@@ -868,13 +878,13 @@
|
|
|
868
878
|
*/
|
|
869
879
|
class Directive {
|
|
870
880
|
constructor() {
|
|
871
|
-
this.node = this.
|
|
881
|
+
this.node = this.init();
|
|
872
882
|
}
|
|
873
883
|
/**
|
|
874
884
|
* Represents a directive button element used to toggle dropdown state.
|
|
875
885
|
* Initializes a clickable node with appropriate ARIA attributes for accessibility.
|
|
876
886
|
*/
|
|
877
|
-
|
|
887
|
+
init() {
|
|
878
888
|
// Libs.nodeCreator returns Element, but this node is always an HTMLElement in practice.
|
|
879
889
|
return Libs.nodeCreator({
|
|
880
890
|
node: "div",
|
|
@@ -906,8 +916,8 @@
|
|
|
906
916
|
this.nodeMounted = null;
|
|
907
917
|
this.node = null;
|
|
908
918
|
this.options = null;
|
|
909
|
-
this.
|
|
910
|
-
this.
|
|
919
|
+
this.actionOnSelectAll = [];
|
|
920
|
+
this.actionOnDeSelectAll = [];
|
|
911
921
|
if (options)
|
|
912
922
|
this.init(options);
|
|
913
923
|
}
|
|
@@ -928,7 +938,7 @@
|
|
|
928
938
|
classList: "selective-ui-option-handle-item",
|
|
929
939
|
textContent: options.textSelectAll,
|
|
930
940
|
onclick: () => {
|
|
931
|
-
iEvents.callFunctions(this.
|
|
941
|
+
iEvents.callFunctions(this.actionOnSelectAll);
|
|
932
942
|
},
|
|
933
943
|
},
|
|
934
944
|
},
|
|
@@ -938,7 +948,7 @@
|
|
|
938
948
|
classList: "selective-ui-option-handle-item",
|
|
939
949
|
textContent: options.textDeselectAll,
|
|
940
950
|
onclick: () => {
|
|
941
|
-
iEvents.callFunctions(this.
|
|
951
|
+
iEvents.callFunctions(this.actionOnDeSelectAll);
|
|
942
952
|
},
|
|
943
953
|
},
|
|
944
954
|
},
|
|
@@ -993,7 +1003,7 @@
|
|
|
993
1003
|
*/
|
|
994
1004
|
OnSelectAll(action = null) {
|
|
995
1005
|
if (typeof action === "function")
|
|
996
|
-
this.
|
|
1006
|
+
this.actionOnSelectAll.push(action);
|
|
997
1007
|
}
|
|
998
1008
|
/**
|
|
999
1009
|
* Registers a callback to be executed when "Deselect All" is clicked.
|
|
@@ -1002,7 +1012,7 @@
|
|
|
1002
1012
|
*/
|
|
1003
1013
|
OnDeSelectAll(action = null) {
|
|
1004
1014
|
if (typeof action === "function")
|
|
1005
|
-
this.
|
|
1015
|
+
this.actionOnDeSelectAll.push(action);
|
|
1006
1016
|
}
|
|
1007
1017
|
}
|
|
1008
1018
|
|
|
@@ -1135,10 +1145,10 @@
|
|
|
1135
1145
|
constructor() {
|
|
1136
1146
|
this.isInit = false;
|
|
1137
1147
|
this.element = null;
|
|
1138
|
-
this.
|
|
1139
|
-
this.
|
|
1148
|
+
this.resizeObserver = null;
|
|
1149
|
+
this.mutationObserver = null;
|
|
1140
1150
|
this.isInit = true;
|
|
1141
|
-
this.
|
|
1151
|
+
this.boundUpdateChanged = this.updateChanged.bind(this);
|
|
1142
1152
|
}
|
|
1143
1153
|
/**
|
|
1144
1154
|
* Callback invoked when the observed element's metrics change.
|
|
@@ -1152,7 +1162,7 @@
|
|
|
1152
1162
|
* Computes the current metrics of the bound element (bounding rect + computed styles)
|
|
1153
1163
|
* and forwards them to `onChanged(metrics)`.
|
|
1154
1164
|
*/
|
|
1155
|
-
|
|
1165
|
+
updateChanged() {
|
|
1156
1166
|
const el = this.element;
|
|
1157
1167
|
if (!el || typeof el.getBoundingClientRect !== "function") {
|
|
1158
1168
|
const defaultMetrics = {
|
|
@@ -1201,7 +1211,7 @@
|
|
|
1201
1211
|
* Manually triggers a metrics computation and notification via `onChanged`.
|
|
1202
1212
|
*/
|
|
1203
1213
|
trigger() {
|
|
1204
|
-
this.
|
|
1214
|
+
this.updateChanged();
|
|
1205
1215
|
}
|
|
1206
1216
|
/**
|
|
1207
1217
|
* Starts observing the provided element for resize and style/class mutations,
|
|
@@ -1215,18 +1225,18 @@
|
|
|
1215
1225
|
throw new Error("Invalid element");
|
|
1216
1226
|
}
|
|
1217
1227
|
this.element = element;
|
|
1218
|
-
this.
|
|
1219
|
-
this.
|
|
1220
|
-
this.
|
|
1221
|
-
this.
|
|
1228
|
+
this.resizeObserver = new ResizeObserver(this.boundUpdateChanged);
|
|
1229
|
+
this.resizeObserver.observe(element);
|
|
1230
|
+
this.mutationObserver = new MutationObserver(this.boundUpdateChanged);
|
|
1231
|
+
this.mutationObserver.observe(element, {
|
|
1222
1232
|
attributes: true,
|
|
1223
1233
|
attributeFilter: ["style", "class"],
|
|
1224
1234
|
});
|
|
1225
|
-
window.addEventListener("scroll", this.
|
|
1226
|
-
window.addEventListener("resize", this.
|
|
1235
|
+
window.addEventListener("scroll", this.boundUpdateChanged, true);
|
|
1236
|
+
window.addEventListener("resize", this.boundUpdateChanged);
|
|
1227
1237
|
if (window.visualViewport) {
|
|
1228
|
-
window.visualViewport.addEventListener("resize", this.
|
|
1229
|
-
window.visualViewport.addEventListener("scroll", this.
|
|
1238
|
+
window.visualViewport.addEventListener("resize", this.boundUpdateChanged);
|
|
1239
|
+
window.visualViewport.addEventListener("scroll", this.boundUpdateChanged);
|
|
1230
1240
|
}
|
|
1231
1241
|
}
|
|
1232
1242
|
/**
|
|
@@ -1234,17 +1244,17 @@
|
|
|
1234
1244
|
* and releases internal observer resources.
|
|
1235
1245
|
*/
|
|
1236
1246
|
disconnect() {
|
|
1237
|
-
this.
|
|
1238
|
-
this.
|
|
1247
|
+
this.resizeObserver?.disconnect();
|
|
1248
|
+
this.mutationObserver?.disconnect();
|
|
1239
1249
|
this.onChanged = (_metrics) => { };
|
|
1240
|
-
window.removeEventListener("scroll", this.
|
|
1241
|
-
window.removeEventListener("resize", this.
|
|
1250
|
+
window.removeEventListener("scroll", this.boundUpdateChanged, true);
|
|
1251
|
+
window.removeEventListener("resize", this.boundUpdateChanged);
|
|
1242
1252
|
if (window.visualViewport) {
|
|
1243
|
-
window.visualViewport.removeEventListener("resize", this.
|
|
1244
|
-
window.visualViewport.removeEventListener("scroll", this.
|
|
1253
|
+
window.visualViewport.removeEventListener("resize", this.boundUpdateChanged);
|
|
1254
|
+
window.visualViewport.removeEventListener("scroll", this.boundUpdateChanged);
|
|
1245
1255
|
}
|
|
1246
|
-
this.
|
|
1247
|
-
this.
|
|
1256
|
+
this.resizeObserver = null;
|
|
1257
|
+
this.mutationObserver = null;
|
|
1248
1258
|
this.element = null;
|
|
1249
1259
|
}
|
|
1250
1260
|
}
|
|
@@ -1266,22 +1276,22 @@
|
|
|
1266
1276
|
this.isCreated = false;
|
|
1267
1277
|
this.optionAdapter = null;
|
|
1268
1278
|
this.node = null;
|
|
1269
|
-
this.
|
|
1270
|
-
this.
|
|
1271
|
-
this.
|
|
1279
|
+
this.effSvc = null;
|
|
1280
|
+
this.resizeObser = null;
|
|
1281
|
+
this.parent = null;
|
|
1272
1282
|
this.optionHandle = null;
|
|
1273
1283
|
this.emptyState = null;
|
|
1274
1284
|
this.loadingState = null;
|
|
1275
1285
|
this.recyclerView = null;
|
|
1276
|
-
this.
|
|
1277
|
-
this.
|
|
1278
|
-
this.
|
|
1286
|
+
this.optionsContainer = null;
|
|
1287
|
+
this.scrollListener = null;
|
|
1288
|
+
this.hideLoadHandle = null;
|
|
1279
1289
|
this.virtualScrollConfig = {
|
|
1280
1290
|
estimateItemHeight: 36,
|
|
1281
1291
|
overscan: 8,
|
|
1282
1292
|
dynamicHeights: true
|
|
1283
1293
|
};
|
|
1284
|
-
this.
|
|
1294
|
+
this.modelManager = modelManager;
|
|
1285
1295
|
if (select && options) {
|
|
1286
1296
|
this.init(select, options);
|
|
1287
1297
|
}
|
|
@@ -1294,7 +1304,7 @@
|
|
|
1294
1304
|
* @param {object} options - Configuration for panel, IDs, multiple mode, and texts.
|
|
1295
1305
|
*/
|
|
1296
1306
|
init(select, options) {
|
|
1297
|
-
if (!this.
|
|
1307
|
+
if (!this.modelManager)
|
|
1298
1308
|
throw new Error("Popup requires a ModelManager instance.");
|
|
1299
1309
|
this.optionHandle = new OptionHandle(options);
|
|
1300
1310
|
this.emptyState = new EmptyState(options);
|
|
@@ -1322,8 +1332,8 @@
|
|
|
1322
1332
|
},
|
|
1323
1333
|
}, null);
|
|
1324
1334
|
this.node = nodeMounted.view;
|
|
1325
|
-
this.
|
|
1326
|
-
this.
|
|
1335
|
+
this.optionsContainer = nodeMounted.tags.OptionsContainer;
|
|
1336
|
+
this.parent = Libs.getBinderMap(select);
|
|
1327
1337
|
this.options = options;
|
|
1328
1338
|
const recyclerViewOpt = options.virtualScroll
|
|
1329
1339
|
? {
|
|
@@ -1334,8 +1344,8 @@
|
|
|
1334
1344
|
}
|
|
1335
1345
|
: {};
|
|
1336
1346
|
// Load ModelManager resources into container
|
|
1337
|
-
this.
|
|
1338
|
-
const MMResources = this.
|
|
1347
|
+
this.modelManager.load(this.optionsContainer, { isMultiple: options.multiple }, recyclerViewOpt);
|
|
1348
|
+
const MMResources = this.modelManager.getResources();
|
|
1339
1349
|
this.optionAdapter = MMResources.adapter;
|
|
1340
1350
|
this.recyclerView = MMResources.recyclerView;
|
|
1341
1351
|
this.optionHandle.OnSelectAll(() => {
|
|
@@ -1344,21 +1354,21 @@
|
|
|
1344
1354
|
this.optionHandle.OnDeSelectAll(() => {
|
|
1345
1355
|
MMResources.adapter.checkAll(false);
|
|
1346
1356
|
});
|
|
1347
|
-
this.
|
|
1357
|
+
this.setupEmptyStateLogic();
|
|
1348
1358
|
}
|
|
1349
1359
|
/**
|
|
1350
1360
|
* Shows the loading state and temporarily skips model events.
|
|
1351
1361
|
* Adjusts size based on current visibility stats and triggers a resize.
|
|
1352
1362
|
*/
|
|
1353
1363
|
async showLoading() {
|
|
1354
|
-
if (!this.options || !this.loadingState || !this.optionHandle || !this.optionAdapter || !this.
|
|
1364
|
+
if (!this.options || !this.loadingState || !this.optionHandle || !this.optionAdapter || !this.modelManager)
|
|
1355
1365
|
return;
|
|
1356
|
-
if (this.
|
|
1357
|
-
clearTimeout(this.
|
|
1358
|
-
this.
|
|
1366
|
+
if (this.hideLoadHandle)
|
|
1367
|
+
clearTimeout(this.hideLoadHandle);
|
|
1368
|
+
this.modelManager.skipEvent(true);
|
|
1359
1369
|
if (Libs.string2Boolean(this.options.loadingfield) === false)
|
|
1360
1370
|
return;
|
|
1361
|
-
// this.
|
|
1371
|
+
// this.updateEmptyState({isEmpty: false, hasVisible: true});
|
|
1362
1372
|
this.emptyState.hide();
|
|
1363
1373
|
this.loadingState.show(this.optionAdapter.getVisibilityStats().hasVisible);
|
|
1364
1374
|
// this.optionHandle.hide();
|
|
@@ -1369,15 +1379,15 @@
|
|
|
1369
1379
|
* updates empty state based on adapter visibility stats, and triggers a resize.
|
|
1370
1380
|
*/
|
|
1371
1381
|
async hideLoading() {
|
|
1372
|
-
if (!this.options || !this.loadingState || !this.optionAdapter || !this.
|
|
1382
|
+
if (!this.options || !this.loadingState || !this.optionAdapter || !this.modelManager)
|
|
1373
1383
|
return;
|
|
1374
|
-
if (this.
|
|
1375
|
-
clearTimeout(this.
|
|
1376
|
-
this.
|
|
1377
|
-
this.
|
|
1384
|
+
if (this.hideLoadHandle)
|
|
1385
|
+
clearTimeout(this.hideLoadHandle);
|
|
1386
|
+
this.hideLoadHandle = setTimeout(() => {
|
|
1387
|
+
this.modelManager?.skipEvent(false);
|
|
1378
1388
|
this.loadingState?.hide();
|
|
1379
1389
|
const stats = this.optionAdapter?.getVisibilityStats();
|
|
1380
|
-
this.
|
|
1390
|
+
this.updateEmptyState(stats ?? undefined);
|
|
1381
1391
|
this.triggerResize();
|
|
1382
1392
|
}, 200);
|
|
1383
1393
|
}
|
|
@@ -1385,15 +1395,15 @@
|
|
|
1385
1395
|
* Subscribes to adapter visibility and item changes to keep the empty state in sync.
|
|
1386
1396
|
* Triggers resize when items change to reflect layout updates.
|
|
1387
1397
|
*/
|
|
1388
|
-
|
|
1398
|
+
setupEmptyStateLogic() {
|
|
1389
1399
|
if (!this.optionAdapter)
|
|
1390
1400
|
return;
|
|
1391
1401
|
this.optionAdapter.onVisibilityChanged((stats) => {
|
|
1392
|
-
this.
|
|
1402
|
+
this.updateEmptyState(stats);
|
|
1393
1403
|
});
|
|
1394
1404
|
this.optionAdapter.onPropChanged("items", () => {
|
|
1395
1405
|
const stats = this.optionAdapter.getVisibilityStats();
|
|
1396
|
-
this.
|
|
1406
|
+
this.updateEmptyState(stats);
|
|
1397
1407
|
this.triggerResize();
|
|
1398
1408
|
});
|
|
1399
1409
|
}
|
|
@@ -1403,23 +1413,23 @@
|
|
|
1403
1413
|
*
|
|
1404
1414
|
* @param {VisibilityStats|undefined} stats - Visibility stats; computed if omitted.
|
|
1405
1415
|
*/
|
|
1406
|
-
|
|
1407
|
-
if (!this.optionAdapter || !this.emptyState || !this.optionHandle || !this.
|
|
1416
|
+
updateEmptyState(stats) {
|
|
1417
|
+
if (!this.optionAdapter || !this.emptyState || !this.optionHandle || !this.optionsContainer)
|
|
1408
1418
|
return;
|
|
1409
1419
|
const s = stats ?? this.optionAdapter.getVisibilityStats();
|
|
1410
1420
|
if (s.isEmpty) {
|
|
1411
1421
|
this.emptyState.show("nodata");
|
|
1412
|
-
this.
|
|
1422
|
+
this.optionsContainer.classList.add("hide");
|
|
1413
1423
|
this.optionHandle.hide();
|
|
1414
1424
|
}
|
|
1415
1425
|
else if (!s.hasVisible) {
|
|
1416
1426
|
this.emptyState.show("notfound");
|
|
1417
|
-
this.
|
|
1427
|
+
this.optionsContainer.classList.add("hide");
|
|
1418
1428
|
this.optionHandle.hide();
|
|
1419
1429
|
}
|
|
1420
1430
|
else {
|
|
1421
1431
|
this.emptyState.hide();
|
|
1422
|
-
this.
|
|
1432
|
+
this.optionsContainer.classList.remove("hide");
|
|
1423
1433
|
this.optionHandle.refresh();
|
|
1424
1434
|
}
|
|
1425
1435
|
}
|
|
@@ -1439,20 +1449,20 @@
|
|
|
1439
1449
|
* Injects an effector service used to perform side effects (e.g., animations or external actions).
|
|
1440
1450
|
*/
|
|
1441
1451
|
setupEffector(effectorSvc) {
|
|
1442
|
-
this.
|
|
1452
|
+
this.effSvc = effectorSvc;
|
|
1443
1453
|
}
|
|
1444
1454
|
/**
|
|
1445
1455
|
* Opens the popup: creates and attaches DOM if needed, initializes observers and effector,
|
|
1446
1456
|
* computes position and dimensions, and runs expand animation. Invokes callback on completion.
|
|
1447
1457
|
*/
|
|
1448
1458
|
open(callback = null, isShowEmptyState) {
|
|
1449
|
-
if (!this.node || !this.options || !this.optionHandle || !this.
|
|
1459
|
+
if (!this.node || !this.options || !this.optionHandle || !this.parent || !this.effSvc)
|
|
1450
1460
|
return;
|
|
1451
1461
|
if (!this.isCreated) {
|
|
1452
1462
|
document.body.appendChild(this.node);
|
|
1453
1463
|
this.isCreated = true;
|
|
1454
|
-
this.
|
|
1455
|
-
this.
|
|
1464
|
+
this.resizeObser = new ResizeObserverService();
|
|
1465
|
+
this.effSvc.setElement(this.node);
|
|
1456
1466
|
this.node.addEventListener("mousedown", (e) => {
|
|
1457
1467
|
e.stopPropagation();
|
|
1458
1468
|
e.preventDefault();
|
|
@@ -1460,11 +1470,11 @@
|
|
|
1460
1470
|
}
|
|
1461
1471
|
this.optionHandle.refresh();
|
|
1462
1472
|
if (isShowEmptyState) {
|
|
1463
|
-
this.
|
|
1473
|
+
this.updateEmptyState();
|
|
1464
1474
|
}
|
|
1465
|
-
const location = this.
|
|
1466
|
-
const { position, top, maxHeight, realHeight } = this.
|
|
1467
|
-
this.
|
|
1475
|
+
const location = this.getParentLocation();
|
|
1476
|
+
const { position, top, maxHeight, realHeight } = this.calculatePosition(location);
|
|
1477
|
+
this.effSvc.expand({
|
|
1468
1478
|
duration: this.options.animationtime,
|
|
1469
1479
|
display: "flex",
|
|
1470
1480
|
width: location.width,
|
|
@@ -1474,14 +1484,14 @@
|
|
|
1474
1484
|
realHeight,
|
|
1475
1485
|
position,
|
|
1476
1486
|
onComplete: () => {
|
|
1477
|
-
if (!this.
|
|
1487
|
+
if (!this.resizeObser || !this.parent)
|
|
1478
1488
|
return;
|
|
1479
|
-
this.
|
|
1489
|
+
this.resizeObser.onChanged = (_metrics) => {
|
|
1480
1490
|
// Recompute from parent each time to keep behavior identical.
|
|
1481
|
-
const loc = this.
|
|
1482
|
-
this.
|
|
1491
|
+
const loc = this.getParentLocation();
|
|
1492
|
+
this.handleResize(loc);
|
|
1483
1493
|
};
|
|
1484
|
-
this.
|
|
1494
|
+
this.resizeObser.connect(this.parent.container.tags.ViewPanel);
|
|
1485
1495
|
callback?.();
|
|
1486
1496
|
const rv = this.recyclerView;
|
|
1487
1497
|
rv?.resume?.();
|
|
@@ -1493,12 +1503,12 @@
|
|
|
1493
1503
|
* Safely no-ops if the popup has not been created.
|
|
1494
1504
|
*/
|
|
1495
1505
|
close(callback = null) {
|
|
1496
|
-
if (!this.isCreated || !this.options || !this.
|
|
1506
|
+
if (!this.isCreated || !this.options || !this.resizeObser || !this.effSvc)
|
|
1497
1507
|
return;
|
|
1498
1508
|
const rv = this.recyclerView;
|
|
1499
1509
|
rv?.suspend?.();
|
|
1500
|
-
this.
|
|
1501
|
-
this.
|
|
1510
|
+
this.resizeObser.disconnect();
|
|
1511
|
+
this.effSvc.collapse({
|
|
1502
1512
|
duration: this.options.animationtime,
|
|
1503
1513
|
onComplete: callback ?? undefined,
|
|
1504
1514
|
});
|
|
@@ -1509,7 +1519,7 @@
|
|
|
1509
1519
|
*/
|
|
1510
1520
|
triggerResize() {
|
|
1511
1521
|
if (this.isCreated)
|
|
1512
|
-
this.
|
|
1522
|
+
this.resizeObser?.trigger();
|
|
1513
1523
|
}
|
|
1514
1524
|
/**
|
|
1515
1525
|
* Enables infinite scroll by listening to container scroll events and loading more data
|
|
@@ -1521,7 +1531,7 @@
|
|
|
1521
1531
|
setupInfiniteScroll(searchController, _options) {
|
|
1522
1532
|
if (!this.node)
|
|
1523
1533
|
return;
|
|
1524
|
-
this.
|
|
1534
|
+
this.scrollListener = async () => {
|
|
1525
1535
|
const state = searchController.getPaginationState();
|
|
1526
1536
|
if (!state.isPaginationEnabled)
|
|
1527
1537
|
return;
|
|
@@ -1539,7 +1549,7 @@
|
|
|
1539
1549
|
}
|
|
1540
1550
|
}
|
|
1541
1551
|
};
|
|
1542
|
-
this.node.addEventListener("scroll", this.
|
|
1552
|
+
this.node.addEventListener("scroll", this.scrollListener);
|
|
1543
1553
|
}
|
|
1544
1554
|
/**
|
|
1545
1555
|
* Completely tear down the popup instance and release all resources.
|
|
@@ -1554,24 +1564,24 @@
|
|
|
1554
1564
|
* Safe to call multiple times; all operations are guarded via optional chaining.
|
|
1555
1565
|
*/
|
|
1556
1566
|
detroy() {
|
|
1557
|
-
if (this.
|
|
1558
|
-
clearTimeout(this.
|
|
1559
|
-
this.
|
|
1567
|
+
if (this.hideLoadHandle) {
|
|
1568
|
+
clearTimeout(this.hideLoadHandle);
|
|
1569
|
+
this.hideLoadHandle = null;
|
|
1560
1570
|
}
|
|
1561
|
-
if (this.node && this.
|
|
1562
|
-
this.node.removeEventListener("scroll", this.
|
|
1563
|
-
this.
|
|
1571
|
+
if (this.node && this.scrollListener) {
|
|
1572
|
+
this.node.removeEventListener("scroll", this.scrollListener);
|
|
1573
|
+
this.scrollListener = null;
|
|
1564
1574
|
}
|
|
1565
1575
|
try {
|
|
1566
|
-
this.
|
|
1576
|
+
this.resizeObser?.disconnect();
|
|
1567
1577
|
}
|
|
1568
1578
|
catch (_) { }
|
|
1569
|
-
this.
|
|
1579
|
+
this.resizeObser = null;
|
|
1570
1580
|
try {
|
|
1571
|
-
this.
|
|
1581
|
+
this.effSvc?.setElement?.(null);
|
|
1572
1582
|
}
|
|
1573
1583
|
catch (_) { }
|
|
1574
|
-
this.
|
|
1584
|
+
this.effSvc = null;
|
|
1575
1585
|
if (this.node) {
|
|
1576
1586
|
try {
|
|
1577
1587
|
const clone = this.node.cloneNode(true);
|
|
@@ -1583,20 +1593,20 @@
|
|
|
1583
1593
|
}
|
|
1584
1594
|
}
|
|
1585
1595
|
this.node = null;
|
|
1586
|
-
this.
|
|
1596
|
+
this.optionsContainer = null;
|
|
1587
1597
|
try {
|
|
1588
|
-
this.
|
|
1598
|
+
this.modelManager?.skipEvent?.(false);
|
|
1589
1599
|
this.recyclerView?.clear?.();
|
|
1590
1600
|
this.recyclerView = null;
|
|
1591
1601
|
this.optionAdapter = null;
|
|
1592
1602
|
this.node.remove();
|
|
1593
1603
|
}
|
|
1594
1604
|
catch (_) { }
|
|
1595
|
-
this.
|
|
1605
|
+
this.modelManager = null;
|
|
1596
1606
|
this.optionHandle = null;
|
|
1597
1607
|
this.emptyState = null;
|
|
1598
1608
|
this.loadingState = null;
|
|
1599
|
-
this.
|
|
1609
|
+
this.parent = null;
|
|
1600
1610
|
this.options = null;
|
|
1601
1611
|
this.isCreated = false;
|
|
1602
1612
|
}
|
|
@@ -1604,8 +1614,8 @@
|
|
|
1604
1614
|
* Computes the parent panel's location and box metrics, including size, position,
|
|
1605
1615
|
* padding, and border, accounting for iOS visual viewport offsets.
|
|
1606
1616
|
*/
|
|
1607
|
-
|
|
1608
|
-
const viewPanel = this.
|
|
1617
|
+
getParentLocation() {
|
|
1618
|
+
const viewPanel = this.parent.container.tags.ViewPanel;
|
|
1609
1619
|
const rect = viewPanel.getBoundingClientRect();
|
|
1610
1620
|
const style = window.getComputedStyle(viewPanel);
|
|
1611
1621
|
return {
|
|
@@ -1631,13 +1641,13 @@
|
|
|
1631
1641
|
* Determines popup placement (top/bottom) and height constraints based on available viewport space,
|
|
1632
1642
|
* content size, and configured min/max heights; returns final position, top, and heights.
|
|
1633
1643
|
*/
|
|
1634
|
-
|
|
1644
|
+
calculatePosition(location) {
|
|
1635
1645
|
const vv = window.visualViewport;
|
|
1636
1646
|
const is_ios = Libs.IsIOS();
|
|
1637
1647
|
const viewportHeight = vv?.height ?? window.innerHeight;
|
|
1638
1648
|
const gap = 3;
|
|
1639
1649
|
const safeMargin = 15;
|
|
1640
|
-
const dimensions = this.
|
|
1650
|
+
const dimensions = this.effSvc.getHiddenDimensions("flex");
|
|
1641
1651
|
const contentHeight = dimensions.scrollHeight;
|
|
1642
1652
|
const configMaxHeight = parseFloat(this.options?.panelHeight ?? "220") || 220;
|
|
1643
1653
|
const configMinHeight = parseFloat(this.options?.panelMinHeight ?? "100") || 100;
|
|
@@ -1676,11 +1686,11 @@
|
|
|
1676
1686
|
* Handles parent resize events by recalculating placement and dimensions,
|
|
1677
1687
|
* then animates the popup to the new size and position.
|
|
1678
1688
|
*/
|
|
1679
|
-
|
|
1680
|
-
if (!this.options || !this.
|
|
1689
|
+
handleResize(location) {
|
|
1690
|
+
if (!this.options || !this.effSvc)
|
|
1681
1691
|
return;
|
|
1682
|
-
const { position, top, maxHeight, realHeight } = this.
|
|
1683
|
-
this.
|
|
1692
|
+
const { position, top, maxHeight, realHeight } = this.calculatePosition(location);
|
|
1693
|
+
this.effSvc.resize({
|
|
1684
1694
|
duration: this.options.animationtime,
|
|
1685
1695
|
width: location.width,
|
|
1686
1696
|
left: location.left,
|
|
@@ -1854,8 +1864,8 @@
|
|
|
1854
1864
|
* @param {string|HTMLElement|null} [query] - A CSS selector or the target element to control.
|
|
1855
1865
|
*/
|
|
1856
1866
|
constructor(query = null) {
|
|
1857
|
-
this.
|
|
1858
|
-
this.
|
|
1867
|
+
this.timeOut = null;
|
|
1868
|
+
this.resizeTimeout = null;
|
|
1859
1869
|
this._isAnimating = false;
|
|
1860
1870
|
if (query)
|
|
1861
1871
|
this.setElement(query);
|
|
@@ -1882,13 +1892,13 @@
|
|
|
1882
1892
|
* @returns {this} - The effector instance for chaining.
|
|
1883
1893
|
*/
|
|
1884
1894
|
cancel() {
|
|
1885
|
-
if (this.
|
|
1886
|
-
clearTimeout(this.
|
|
1887
|
-
this.
|
|
1895
|
+
if (this.timeOut) {
|
|
1896
|
+
clearTimeout(this.timeOut);
|
|
1897
|
+
this.timeOut = null;
|
|
1888
1898
|
}
|
|
1889
|
-
if (this.
|
|
1890
|
-
clearTimeout(this.
|
|
1891
|
-
this.
|
|
1899
|
+
if (this.resizeTimeout) {
|
|
1900
|
+
clearTimeout(this.resizeTimeout);
|
|
1901
|
+
this.resizeTimeout = null;
|
|
1892
1902
|
}
|
|
1893
1903
|
this._isAnimating = false;
|
|
1894
1904
|
return this;
|
|
@@ -1963,7 +1973,7 @@
|
|
|
1963
1973
|
opacity: "1",
|
|
1964
1974
|
overflow: isScrollable ? "auto" : "hidden",
|
|
1965
1975
|
});
|
|
1966
|
-
this.
|
|
1976
|
+
this.timeOut = setTimeout(() => {
|
|
1967
1977
|
this.element.style.transition = "none";
|
|
1968
1978
|
this._isAnimating = false;
|
|
1969
1979
|
onComplete?.();
|
|
@@ -1995,7 +2005,7 @@
|
|
|
1995
2005
|
opacity: "0",
|
|
1996
2006
|
overflow: isScrollable ? "auto" : "hidden",
|
|
1997
2007
|
});
|
|
1998
|
-
this.
|
|
2008
|
+
this.timeOut = setTimeout(() => {
|
|
1999
2009
|
Object.assign(this.element.style, {
|
|
2000
2010
|
display: "none",
|
|
2001
2011
|
transition: "none",
|
|
@@ -2031,7 +2041,7 @@
|
|
|
2031
2041
|
overflow: "hidden",
|
|
2032
2042
|
});
|
|
2033
2043
|
});
|
|
2034
|
-
this.
|
|
2044
|
+
this.timeOut = setTimeout(() => {
|
|
2035
2045
|
Object.assign(this.element.style, {
|
|
2036
2046
|
width: "",
|
|
2037
2047
|
overflow: "",
|
|
@@ -2065,7 +2075,7 @@
|
|
|
2065
2075
|
overflow: "hidden",
|
|
2066
2076
|
});
|
|
2067
2077
|
});
|
|
2068
|
-
this.
|
|
2078
|
+
this.timeOut = setTimeout(() => {
|
|
2069
2079
|
Object.assign(this.element.style, {
|
|
2070
2080
|
width: "",
|
|
2071
2081
|
overflow: "",
|
|
@@ -2109,7 +2119,7 @@
|
|
|
2109
2119
|
styles.transition = `height ${duration}ms, top ${duration}ms`;
|
|
2110
2120
|
}
|
|
2111
2121
|
else {
|
|
2112
|
-
this.
|
|
2122
|
+
this.resizeTimeout = setTimeout(() => {
|
|
2113
2123
|
if (this.element?.style) {
|
|
2114
2124
|
this.element.style.transition = null;
|
|
2115
2125
|
}
|
|
@@ -2117,7 +2127,7 @@
|
|
|
2117
2127
|
}
|
|
2118
2128
|
Object.assign(this.element.style, styles);
|
|
2119
2129
|
if (animate && (isPositionChanged || heightDiff > 1)) {
|
|
2120
|
-
this.
|
|
2130
|
+
this.resizeTimeout = setTimeout(() => {
|
|
2121
2131
|
this.element.style.transition = null;
|
|
2122
2132
|
if (isPositionChanged)
|
|
2123
2133
|
delete this.element.style.transition;
|
|
@@ -2212,7 +2222,7 @@
|
|
|
2212
2222
|
* Initializes a group model with options and an optional <optgroup> target.
|
|
2213
2223
|
* Reads the label and collapsed state from the target element's attributes/dataset.
|
|
2214
2224
|
*
|
|
2215
|
-
* @param {
|
|
2225
|
+
* @param {SelectiveOptions} options - Configuration for the model.
|
|
2216
2226
|
* @param {HTMLOptGroupElement} [targetElement] - The source <optgroup> element.
|
|
2217
2227
|
*/
|
|
2218
2228
|
constructor(options, targetElement) {
|
|
@@ -2220,7 +2230,7 @@
|
|
|
2220
2230
|
this.label = "";
|
|
2221
2231
|
this.items = [];
|
|
2222
2232
|
this.collapsed = false;
|
|
2223
|
-
this.
|
|
2233
|
+
this.privOnCollapsedChanged = [];
|
|
2224
2234
|
if (targetElement) {
|
|
2225
2235
|
this.label = targetElement.label;
|
|
2226
2236
|
this.collapsed = Libs.string2Boolean(targetElement.dataset?.collapsed);
|
|
@@ -2283,7 +2293,7 @@
|
|
|
2283
2293
|
* @param {(evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void} callback - Listener for collapse changes.
|
|
2284
2294
|
*/
|
|
2285
2295
|
onCollapsedChanged(callback) {
|
|
2286
|
-
this.
|
|
2296
|
+
this.privOnCollapsedChanged.push(callback);
|
|
2287
2297
|
}
|
|
2288
2298
|
/**
|
|
2289
2299
|
* Toggles the group's collapsed state, updates the view, and notifies registered listeners.
|
|
@@ -2291,7 +2301,7 @@
|
|
|
2291
2301
|
toggleCollapse() {
|
|
2292
2302
|
this.collapsed = !this.collapsed;
|
|
2293
2303
|
this.view?.setCollapsed(this.collapsed);
|
|
2294
|
-
iEvents.callEvent([this, this.collapsed], ...this.
|
|
2304
|
+
iEvents.callEvent([this, this.collapsed], ...this.privOnCollapsedChanged);
|
|
2295
2305
|
}
|
|
2296
2306
|
/**
|
|
2297
2307
|
* Adds an option item to this group and sets its back-reference to the group.
|
|
@@ -2337,9 +2347,9 @@
|
|
|
2337
2347
|
*/
|
|
2338
2348
|
constructor(options, targetElement = null, view = null) {
|
|
2339
2349
|
super(options, targetElement, view);
|
|
2340
|
-
this.
|
|
2341
|
-
this.
|
|
2342
|
-
this.
|
|
2350
|
+
this.privOnSelected = [];
|
|
2351
|
+
this.privOnInternalSelected = [];
|
|
2352
|
+
this.privOnVisibilityChanged = [];
|
|
2343
2353
|
this._visible = true;
|
|
2344
2354
|
this._highlighted = false;
|
|
2345
2355
|
this.group = null;
|
|
@@ -2387,7 +2397,7 @@
|
|
|
2387
2397
|
*/
|
|
2388
2398
|
set selected(value) {
|
|
2389
2399
|
this.selectedNonTrigger = value;
|
|
2390
|
-
iEvents.callEvent([this, value], ...this.
|
|
2400
|
+
iEvents.callEvent([this, value], ...this.privOnSelected);
|
|
2391
2401
|
}
|
|
2392
2402
|
/**
|
|
2393
2403
|
* Gets whether the option is currently visible in the UI.
|
|
@@ -2409,7 +2419,7 @@
|
|
|
2409
2419
|
const viewEl = this.view?.getView?.();
|
|
2410
2420
|
if (viewEl)
|
|
2411
2421
|
viewEl.classList.toggle("hide", !value);
|
|
2412
|
-
iEvents.callEvent([this, value], ...this.
|
|
2422
|
+
iEvents.callEvent([this, value], ...this.privOnVisibilityChanged);
|
|
2413
2423
|
}
|
|
2414
2424
|
/**
|
|
2415
2425
|
* Gets the selected state without triggering external listeners (alias of selected).
|
|
@@ -2437,7 +2447,7 @@
|
|
|
2437
2447
|
}
|
|
2438
2448
|
if (this.targetElement)
|
|
2439
2449
|
this.targetElement.selected = value;
|
|
2440
|
-
iEvents.callEvent([this, value], ...this.
|
|
2450
|
+
iEvents.callEvent([this, value], ...this.privOnInternalSelected);
|
|
2441
2451
|
}
|
|
2442
2452
|
/**
|
|
2443
2453
|
* Returns the display text for the option, applying tag translation and optional HTML allowance.
|
|
@@ -2495,7 +2505,7 @@
|
|
|
2495
2505
|
* @param {(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void} callback - Selection listener.
|
|
2496
2506
|
*/
|
|
2497
2507
|
onSelected(callback) {
|
|
2498
|
-
this.
|
|
2508
|
+
this.privOnSelected.push(callback);
|
|
2499
2509
|
}
|
|
2500
2510
|
/**
|
|
2501
2511
|
* Registers a listener invoked when internal selection changes (via setter `selectedNonTrigger`).
|
|
@@ -2503,7 +2513,7 @@
|
|
|
2503
2513
|
* @param {(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void} callback - Internal selection listener.
|
|
2504
2514
|
*/
|
|
2505
2515
|
onInternalSelected(callback) {
|
|
2506
|
-
this.
|
|
2516
|
+
this.privOnInternalSelected.push(callback);
|
|
2507
2517
|
}
|
|
2508
2518
|
/**
|
|
2509
2519
|
* Registers a listener invoked when visibility changes (via setter `visible`).
|
|
@@ -2511,7 +2521,7 @@
|
|
|
2511
2521
|
* @param {(evtToken: IEventCallback, model: OptionModel, visible: boolean) => void} callback - Visibility listener.
|
|
2512
2522
|
*/
|
|
2513
2523
|
onVisibilityChanged(callback) {
|
|
2514
|
-
this.
|
|
2524
|
+
this.privOnVisibilityChanged.push(callback);
|
|
2515
2525
|
}
|
|
2516
2526
|
/**
|
|
2517
2527
|
* Hook called when the target <option> element changes.
|
|
@@ -2552,10 +2562,10 @@
|
|
|
2552
2562
|
* @param {object} options - Configuration object passed to GroupModel/OptionModel and view infrastructure.
|
|
2553
2563
|
*/
|
|
2554
2564
|
constructor(options) {
|
|
2555
|
-
this.
|
|
2556
|
-
this.
|
|
2557
|
-
this.
|
|
2558
|
-
this.
|
|
2565
|
+
this.privModelList = [];
|
|
2566
|
+
this.privAdapterHandle = null;
|
|
2567
|
+
this.privRecyclerViewHandle = null;
|
|
2568
|
+
this.lastFingerprint = null;
|
|
2559
2569
|
this.options = null;
|
|
2560
2570
|
this.options = options;
|
|
2561
2571
|
}
|
|
@@ -2565,7 +2575,7 @@
|
|
|
2565
2575
|
* @param {new TAdapter} adapter - The adapter constructor (class) to instantiate.
|
|
2566
2576
|
*/
|
|
2567
2577
|
setupAdapter(adapter) {
|
|
2568
|
-
this.
|
|
2578
|
+
this.privAdapter = adapter;
|
|
2569
2579
|
}
|
|
2570
2580
|
/**
|
|
2571
2581
|
* Registers the RecyclerView class responsible for hosting and updating item views.
|
|
@@ -2573,7 +2583,7 @@
|
|
|
2573
2583
|
* @param {new RecyclerViewContract<TAdapter>} recyclerView - The recycler view constructor.
|
|
2574
2584
|
*/
|
|
2575
2585
|
setupRecyclerView(recyclerView) {
|
|
2576
|
-
this.
|
|
2586
|
+
this.privRecyclerView = recyclerView;
|
|
2577
2587
|
}
|
|
2578
2588
|
/**
|
|
2579
2589
|
* Checks whether the provided model data differs from the last recorded fingerprint.
|
|
@@ -2584,10 +2594,10 @@
|
|
|
2584
2594
|
* @returns {boolean} True if there are real changes; false otherwise.
|
|
2585
2595
|
*/
|
|
2586
2596
|
hasRealChanges(modelData) {
|
|
2587
|
-
const newFingerprint = this.
|
|
2588
|
-
const hasChanges = newFingerprint !== this.
|
|
2597
|
+
const newFingerprint = this.createFingerprint(modelData);
|
|
2598
|
+
const hasChanges = newFingerprint !== this.lastFingerprint;
|
|
2589
2599
|
if (hasChanges)
|
|
2590
|
-
this.
|
|
2600
|
+
this.lastFingerprint = newFingerprint;
|
|
2591
2601
|
return hasChanges;
|
|
2592
2602
|
}
|
|
2593
2603
|
/**
|
|
@@ -2599,7 +2609,7 @@
|
|
|
2599
2609
|
* @param {Array<HTMLOptionElement|HTMLOptGroupElement>} modelData - The current model data to fingerprint.
|
|
2600
2610
|
* @returns {string} A deterministic fingerprint representing the structure and selection state.
|
|
2601
2611
|
*/
|
|
2602
|
-
|
|
2612
|
+
createFingerprint(modelData) {
|
|
2603
2613
|
return modelData
|
|
2604
2614
|
.map((item) => {
|
|
2605
2615
|
if (item.tagName === "OPTGROUP") {
|
|
@@ -2627,12 +2637,12 @@
|
|
|
2627
2637
|
* @returns {Array<GroupModel|OptionModel>} - The ordered list of group and option models.
|
|
2628
2638
|
*/
|
|
2629
2639
|
createModelResources(modelData) {
|
|
2630
|
-
this.
|
|
2640
|
+
this.privModelList = [];
|
|
2631
2641
|
let currentGroup = null;
|
|
2632
2642
|
modelData.forEach((data) => {
|
|
2633
2643
|
if (data.tagName === "OPTGROUP") {
|
|
2634
2644
|
currentGroup = new GroupModel(this.options, data);
|
|
2635
|
-
this.
|
|
2645
|
+
this.privModelList.push(currentGroup);
|
|
2636
2646
|
}
|
|
2637
2647
|
else if (data.tagName === "OPTION") {
|
|
2638
2648
|
const optionEl = data;
|
|
@@ -2643,12 +2653,12 @@
|
|
|
2643
2653
|
optionModel.group = currentGroup;
|
|
2644
2654
|
}
|
|
2645
2655
|
else {
|
|
2646
|
-
this.
|
|
2656
|
+
this.privModelList.push(optionModel);
|
|
2647
2657
|
currentGroup = null;
|
|
2648
2658
|
}
|
|
2649
2659
|
}
|
|
2650
2660
|
});
|
|
2651
|
-
return this.
|
|
2661
|
+
return this.privModelList;
|
|
2652
2662
|
}
|
|
2653
2663
|
/**
|
|
2654
2664
|
* Replaces the current model list with new data and syncs it into the adapter,
|
|
@@ -2657,11 +2667,11 @@
|
|
|
2657
2667
|
* @param {Array<HTMLOptGroupElement|HTMLOptionElement>} modelData - New source elements to rebuild models from.
|
|
2658
2668
|
*/
|
|
2659
2669
|
replace(modelData) {
|
|
2660
|
-
this.
|
|
2670
|
+
this.lastFingerprint = null;
|
|
2661
2671
|
this.createModelResources(modelData);
|
|
2662
|
-
if (this.
|
|
2672
|
+
if (this.privAdapterHandle) {
|
|
2663
2673
|
// Adapter expects TModel[], but this manager's list is GroupModel|OptionModel.
|
|
2664
|
-
this.
|
|
2674
|
+
this.privAdapterHandle.syncFromSource(this.privModelList);
|
|
2665
2675
|
}
|
|
2666
2676
|
this.refresh(false);
|
|
2667
2677
|
}
|
|
@@ -2670,7 +2680,7 @@
|
|
|
2670
2680
|
* typically used after external updates to model data.
|
|
2671
2681
|
*/
|
|
2672
2682
|
notify() {
|
|
2673
|
-
if (!this.
|
|
2683
|
+
if (!this.privAdapterHandle)
|
|
2674
2684
|
return;
|
|
2675
2685
|
this.refresh(false);
|
|
2676
2686
|
}
|
|
@@ -2679,11 +2689,11 @@
|
|
|
2679
2689
|
* and applies optional configuration overrides for adapter and recyclerView.
|
|
2680
2690
|
*/
|
|
2681
2691
|
load(viewElement, adapterOpt = {}, recyclerViewOpt = {}) {
|
|
2682
|
-
this.
|
|
2683
|
-
Object.assign(this.
|
|
2684
|
-
this.
|
|
2685
|
-
Object.assign(this.
|
|
2686
|
-
this.
|
|
2692
|
+
this.privAdapterHandle = new this.privAdapter(this.privModelList);
|
|
2693
|
+
Object.assign(this.privAdapterHandle, adapterOpt);
|
|
2694
|
+
this.privRecyclerViewHandle = new this.privRecyclerView(viewElement);
|
|
2695
|
+
Object.assign(this.privRecyclerViewHandle, recyclerViewOpt);
|
|
2696
|
+
this.privRecyclerViewHandle.setAdapter(this.privAdapterHandle);
|
|
2687
2697
|
}
|
|
2688
2698
|
/**
|
|
2689
2699
|
* Diffs existing models against new <optgroup>/<option> data to update in place:
|
|
@@ -2693,7 +2703,7 @@
|
|
|
2693
2703
|
update(modelData) {
|
|
2694
2704
|
if (!this.hasRealChanges(modelData))
|
|
2695
2705
|
return;
|
|
2696
|
-
const oldModels = this.
|
|
2706
|
+
const oldModels = this.privModelList;
|
|
2697
2707
|
const newModels = [];
|
|
2698
2708
|
const oldGroupMap = new Map();
|
|
2699
2709
|
const oldOptionMap = new Map();
|
|
@@ -2773,9 +2783,9 @@
|
|
|
2773
2783
|
isUpdate = false;
|
|
2774
2784
|
removedOption.remove();
|
|
2775
2785
|
});
|
|
2776
|
-
this.
|
|
2777
|
-
if (this.
|
|
2778
|
-
this.
|
|
2786
|
+
this.privModelList = newModels;
|
|
2787
|
+
if (this.privAdapterHandle) {
|
|
2788
|
+
this.privAdapterHandle.updateData(this.privModelList);
|
|
2779
2789
|
}
|
|
2780
2790
|
this.onUpdated();
|
|
2781
2791
|
this.refresh(isUpdate);
|
|
@@ -2791,8 +2801,8 @@
|
|
|
2791
2801
|
* @param {boolean} value - True to skip events; false to restore normal behavior.
|
|
2792
2802
|
*/
|
|
2793
2803
|
skipEvent(value) {
|
|
2794
|
-
if (this.
|
|
2795
|
-
this.
|
|
2804
|
+
if (this.privAdapterHandle)
|
|
2805
|
+
this.privAdapterHandle.isSkipEvent = value;
|
|
2796
2806
|
}
|
|
2797
2807
|
/**
|
|
2798
2808
|
* Re-renders the recycler view if present and invokes the post-refresh hook.
|
|
@@ -2801,9 +2811,9 @@
|
|
|
2801
2811
|
* @param isUpdate - Indicates if this refresh is due to an update operation.
|
|
2802
2812
|
*/
|
|
2803
2813
|
refresh(isUpdate) {
|
|
2804
|
-
if (!this.
|
|
2814
|
+
if (!this.privRecyclerViewHandle)
|
|
2805
2815
|
return;
|
|
2806
|
-
this.
|
|
2816
|
+
this.privRecyclerViewHandle.refresh(isUpdate);
|
|
2807
2817
|
this.onUpdated();
|
|
2808
2818
|
}
|
|
2809
2819
|
/**
|
|
@@ -2812,9 +2822,9 @@
|
|
|
2812
2822
|
*/
|
|
2813
2823
|
getResources() {
|
|
2814
2824
|
return {
|
|
2815
|
-
modelList: this.
|
|
2816
|
-
adapter: this.
|
|
2817
|
-
recyclerView: this.
|
|
2825
|
+
modelList: this.privModelList,
|
|
2826
|
+
adapter: this.privAdapterHandle,
|
|
2827
|
+
recyclerView: this.privRecyclerViewHandle,
|
|
2818
2828
|
};
|
|
2819
2829
|
}
|
|
2820
2830
|
/**
|
|
@@ -2822,14 +2832,14 @@
|
|
|
2822
2832
|
* enabling observers to react before a change is applied.
|
|
2823
2833
|
*/
|
|
2824
2834
|
triggerChanging(event_name) {
|
|
2825
|
-
this.
|
|
2835
|
+
this.privAdapterHandle?.changingProp(event_name);
|
|
2826
2836
|
}
|
|
2827
2837
|
/**
|
|
2828
2838
|
* Triggers the adapter's post-change pipeline for a named event,
|
|
2829
2839
|
* notifying observers after a change has been applied.
|
|
2830
2840
|
*/
|
|
2831
2841
|
triggerChanged(event_name) {
|
|
2832
|
-
this.
|
|
2842
|
+
this.privAdapterHandle?.changeProp(event_name);
|
|
2833
2843
|
}
|
|
2834
2844
|
}
|
|
2835
2845
|
|
|
@@ -2920,6 +2930,7 @@
|
|
|
2920
2930
|
this.selectUIMask = null;
|
|
2921
2931
|
this.parentMask = null;
|
|
2922
2932
|
this.modelManager = null;
|
|
2933
|
+
this.modelDatas = [];
|
|
2923
2934
|
if (options)
|
|
2924
2935
|
this.init(options);
|
|
2925
2936
|
}
|
|
@@ -2959,7 +2970,10 @@
|
|
|
2959
2970
|
* Keeps the accessory box aligned relative to the parent mask.
|
|
2960
2971
|
*/
|
|
2961
2972
|
refreshLocation() {
|
|
2962
|
-
if (!this.parentMask ||
|
|
2973
|
+
if (!this.parentMask ||
|
|
2974
|
+
!this.node ||
|
|
2975
|
+
!this.selectUIMask ||
|
|
2976
|
+
!this.options)
|
|
2963
2977
|
return;
|
|
2964
2978
|
const ref = this.options.accessoryStyle === "top"
|
|
2965
2979
|
? this.selectUIMask
|
|
@@ -2985,7 +2999,6 @@
|
|
|
2985
2999
|
return;
|
|
2986
3000
|
this.node.replaceChildren();
|
|
2987
3001
|
if (modelDatas.length > 0 && this.options.multiple) {
|
|
2988
|
-
this.node.classList.remove("hide");
|
|
2989
3002
|
modelDatas.forEach((modelData) => {
|
|
2990
3003
|
Libs.mountNode({
|
|
2991
3004
|
AccessoryItem: {
|
|
@@ -3020,10 +3033,26 @@
|
|
|
3020
3033
|
});
|
|
3021
3034
|
}
|
|
3022
3035
|
else {
|
|
3023
|
-
|
|
3036
|
+
modelDatas = [];
|
|
3024
3037
|
}
|
|
3038
|
+
this.modelDatas = modelDatas;
|
|
3039
|
+
this.refreshDisplay();
|
|
3025
3040
|
iEvents.trigger(window, "resize");
|
|
3026
3041
|
}
|
|
3042
|
+
refreshDisplay() {
|
|
3043
|
+
if (this.options?.accessoryVisible && this.modelDatas.length > 0 && this.options.multiple) {
|
|
3044
|
+
this.show();
|
|
3045
|
+
}
|
|
3046
|
+
else {
|
|
3047
|
+
this.hide();
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
show() {
|
|
3051
|
+
this.node.classList.remove("hide");
|
|
3052
|
+
}
|
|
3053
|
+
hide() {
|
|
3054
|
+
this.node.classList.add("hide");
|
|
3055
|
+
}
|
|
3027
3056
|
}
|
|
3028
3057
|
|
|
3029
3058
|
class SearchController {
|
|
@@ -3036,11 +3065,11 @@
|
|
|
3036
3065
|
* @param {SelectBox} selectBox - SelectBox handle.
|
|
3037
3066
|
*/
|
|
3038
3067
|
constructor(selectElement, modelManager, selectBox) {
|
|
3039
|
-
this.
|
|
3040
|
-
this.
|
|
3041
|
-
this.
|
|
3042
|
-
this.
|
|
3043
|
-
this.
|
|
3068
|
+
this.ajaxConfig = null;
|
|
3069
|
+
this.abortController = null;
|
|
3070
|
+
this.popup = null;
|
|
3071
|
+
this.selectBox = null;
|
|
3072
|
+
this.paginationState = {
|
|
3044
3073
|
currentPage: 0,
|
|
3045
3074
|
totalPages: 1,
|
|
3046
3075
|
hasMore: false,
|
|
@@ -3048,9 +3077,9 @@
|
|
|
3048
3077
|
currentKeyword: "",
|
|
3049
3078
|
isPaginationEnabled: false,
|
|
3050
3079
|
};
|
|
3051
|
-
this.
|
|
3052
|
-
this.
|
|
3053
|
-
this.
|
|
3080
|
+
this.select = selectElement;
|
|
3081
|
+
this.modelManager = modelManager;
|
|
3082
|
+
this.selectBox = selectBox;
|
|
3054
3083
|
}
|
|
3055
3084
|
/**
|
|
3056
3085
|
* Indicates whether AJAX-based search is configured.
|
|
@@ -3058,7 +3087,7 @@
|
|
|
3058
3087
|
* @returns {boolean} - True if AJAX config is present; false otherwise.
|
|
3059
3088
|
*/
|
|
3060
3089
|
isAjax() {
|
|
3061
|
-
return !!this.
|
|
3090
|
+
return !!this.ajaxConfig;
|
|
3062
3091
|
}
|
|
3063
3092
|
/**
|
|
3064
3093
|
* Load specific options by their values from server
|
|
@@ -3066,14 +3095,14 @@
|
|
|
3066
3095
|
* @returns {Promise<{success: boolean, items: Array, message?: string}>}
|
|
3067
3096
|
*/
|
|
3068
3097
|
async loadByValues(values) {
|
|
3069
|
-
if (!this.
|
|
3098
|
+
if (!this.ajaxConfig) {
|
|
3070
3099
|
return { success: false, items: [], message: "Ajax not configured" };
|
|
3071
3100
|
}
|
|
3072
3101
|
const valuesArray = Array.isArray(values) ? values : [values];
|
|
3073
3102
|
if (valuesArray.length === 0)
|
|
3074
3103
|
return { success: true, items: [] };
|
|
3075
3104
|
try {
|
|
3076
|
-
const cfg = this.
|
|
3105
|
+
const cfg = this.ajaxConfig;
|
|
3077
3106
|
let payload;
|
|
3078
3107
|
if (typeof cfg.dataByValues === "function") {
|
|
3079
3108
|
payload = cfg.dataByValues(valuesArray);
|
|
@@ -3082,7 +3111,7 @@
|
|
|
3082
3111
|
payload = {
|
|
3083
3112
|
values: valuesArray.join(","),
|
|
3084
3113
|
load_by_values: "1",
|
|
3085
|
-
...(typeof cfg.data === "function" ? cfg.data.bind(this.
|
|
3114
|
+
...(typeof cfg.data === "function" ? cfg.data.bind(this.selectBox.Selective.find(this.selectBox.container.targetElement))("", 0) : cfg.data ?? {}),
|
|
3086
3115
|
};
|
|
3087
3116
|
}
|
|
3088
3117
|
let response;
|
|
@@ -3102,7 +3131,7 @@
|
|
|
3102
3131
|
if (!response.ok)
|
|
3103
3132
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
3104
3133
|
const data = await response.json();
|
|
3105
|
-
const result = this.
|
|
3134
|
+
const result = this.parseResponse(data);
|
|
3106
3135
|
return { success: true, items: result.items };
|
|
3107
3136
|
}
|
|
3108
3137
|
catch (error) {
|
|
@@ -3116,7 +3145,7 @@
|
|
|
3116
3145
|
* @returns {{existing: string[], missing: string[]}}
|
|
3117
3146
|
*/
|
|
3118
3147
|
checkMissingValues(values) {
|
|
3119
|
-
const allOptions = Array.from(this.
|
|
3148
|
+
const allOptions = Array.from(this.select.options);
|
|
3120
3149
|
const existingValues = allOptions.map((opt) => opt.value);
|
|
3121
3150
|
const existing = values.filter((v) => existingValues.includes(v));
|
|
3122
3151
|
const missing = values.filter((v) => !existingValues.includes(v));
|
|
@@ -3128,7 +3157,7 @@
|
|
|
3128
3157
|
* @param {object} config - AJAX configuration object (e.g., endpoint, headers, query params).
|
|
3129
3158
|
*/
|
|
3130
3159
|
setAjax(config) {
|
|
3131
|
-
this.
|
|
3160
|
+
this.ajaxConfig = config;
|
|
3132
3161
|
}
|
|
3133
3162
|
/**
|
|
3134
3163
|
* Attaches a Popup instance to allow UI updates during search (e.g., loading, resize).
|
|
@@ -3136,34 +3165,34 @@
|
|
|
3136
3165
|
* @param {Popup} popupInstance - The popup used to display search results and loading state.
|
|
3137
3166
|
*/
|
|
3138
3167
|
setPopup(popupInstance) {
|
|
3139
|
-
this.
|
|
3168
|
+
this.popup = popupInstance;
|
|
3140
3169
|
}
|
|
3141
3170
|
/**
|
|
3142
3171
|
* Returns a shallow copy of the current pagination state used for search/infinite scroll.
|
|
3143
3172
|
*/
|
|
3144
3173
|
getPaginationState() {
|
|
3145
|
-
return { ...this.
|
|
3174
|
+
return { ...this.paginationState };
|
|
3146
3175
|
}
|
|
3147
3176
|
/**
|
|
3148
3177
|
* Resets pagination counters while preserving whether pagination is enabled.
|
|
3149
3178
|
* Clears page, totals, loading flags, and current keyword.
|
|
3150
3179
|
*/
|
|
3151
3180
|
resetPagination() {
|
|
3152
|
-
this.
|
|
3181
|
+
this.paginationState = {
|
|
3153
3182
|
currentPage: 0,
|
|
3154
3183
|
totalPages: 1,
|
|
3155
3184
|
hasMore: false,
|
|
3156
3185
|
isLoading: false,
|
|
3157
3186
|
currentKeyword: "",
|
|
3158
|
-
isPaginationEnabled: this.
|
|
3187
|
+
isPaginationEnabled: this.paginationState.isPaginationEnabled,
|
|
3159
3188
|
};
|
|
3160
3189
|
}
|
|
3161
3190
|
/**
|
|
3162
3191
|
* Clears the current keyword and makes all options visible (local reset).
|
|
3163
3192
|
*/
|
|
3164
3193
|
clear() {
|
|
3165
|
-
this.
|
|
3166
|
-
const { modelList } = this.
|
|
3194
|
+
this.paginationState.currentKeyword = "";
|
|
3195
|
+
const { modelList } = this.modelManager.getResources();
|
|
3167
3196
|
const flatOptions = [];
|
|
3168
3197
|
for (const m of modelList) {
|
|
3169
3198
|
if (m instanceof OptionModel)
|
|
@@ -3179,7 +3208,7 @@
|
|
|
3179
3208
|
* Performs a search with either AJAX or local filtering depending on configuration.
|
|
3180
3209
|
*/
|
|
3181
3210
|
async search(keyword, append = false) {
|
|
3182
|
-
if (this.
|
|
3211
|
+
if (this.ajaxConfig)
|
|
3183
3212
|
return this._ajaxSearch(keyword, append);
|
|
3184
3213
|
return this._localSearch(keyword);
|
|
3185
3214
|
}
|
|
@@ -3187,16 +3216,16 @@
|
|
|
3187
3216
|
* Loads the next page for AJAX pagination if enabled and not already loading.
|
|
3188
3217
|
*/
|
|
3189
3218
|
async loadMore() {
|
|
3190
|
-
if (!this.
|
|
3219
|
+
if (!this.ajaxConfig)
|
|
3191
3220
|
return { success: false, message: "Ajax not enabled" };
|
|
3192
|
-
if (this.
|
|
3221
|
+
if (this.paginationState.isLoading)
|
|
3193
3222
|
return { success: false, message: "Already loading" };
|
|
3194
|
-
if (!this.
|
|
3223
|
+
if (!this.paginationState.isPaginationEnabled)
|
|
3195
3224
|
return { success: false, message: "Pagination not enabled" };
|
|
3196
|
-
if (!this.
|
|
3225
|
+
if (!this.paginationState.hasMore)
|
|
3197
3226
|
return { success: false, message: "No more data" };
|
|
3198
|
-
this.
|
|
3199
|
-
return this._ajaxSearch(this.
|
|
3227
|
+
this.paginationState.currentPage++;
|
|
3228
|
+
return this._ajaxSearch(this.paginationState.currentKeyword, true);
|
|
3200
3229
|
}
|
|
3201
3230
|
/**
|
|
3202
3231
|
* Executes a local (in-memory) search by normalizing the keyword (lowercase, non-accent)
|
|
@@ -3204,10 +3233,10 @@
|
|
|
3204
3233
|
*/
|
|
3205
3234
|
async _localSearch(keyword) {
|
|
3206
3235
|
if (this.compareSearchTrigger(keyword))
|
|
3207
|
-
this.
|
|
3236
|
+
this.paginationState.currentKeyword = keyword;
|
|
3208
3237
|
const lower = String(keyword ?? "").toLowerCase();
|
|
3209
3238
|
const lowerNA = Libs.string2normalize(lower);
|
|
3210
|
-
const { modelList } = this.
|
|
3239
|
+
const { modelList } = this.modelManager.getResources();
|
|
3211
3240
|
const flatOptions = [];
|
|
3212
3241
|
for (const m of modelList) {
|
|
3213
3242
|
if (m instanceof OptionModel)
|
|
@@ -3233,29 +3262,29 @@
|
|
|
3233
3262
|
* to determine if a new search should be triggered.
|
|
3234
3263
|
*/
|
|
3235
3264
|
compareSearchTrigger(keyword) {
|
|
3236
|
-
return keyword !== this.
|
|
3265
|
+
return keyword !== this.paginationState.currentKeyword;
|
|
3237
3266
|
}
|
|
3238
3267
|
/**
|
|
3239
3268
|
* Executes an AJAX-based search with optional appending.
|
|
3240
3269
|
*/
|
|
3241
3270
|
async _ajaxSearch(keyword, append = false) {
|
|
3242
|
-
const cfg = this.
|
|
3271
|
+
const cfg = this.ajaxConfig;
|
|
3243
3272
|
if (this.compareSearchTrigger(keyword)) {
|
|
3244
3273
|
this.resetPagination();
|
|
3245
|
-
this.
|
|
3274
|
+
this.paginationState.currentKeyword = keyword;
|
|
3246
3275
|
append = false;
|
|
3247
3276
|
}
|
|
3248
|
-
this.
|
|
3249
|
-
this.
|
|
3250
|
-
this.
|
|
3251
|
-
this.
|
|
3252
|
-
const page = this.
|
|
3253
|
-
const selectedValues = Array.from(this.
|
|
3277
|
+
this.paginationState.isLoading = true;
|
|
3278
|
+
this.popup?.showLoading();
|
|
3279
|
+
this.abortController?.abort();
|
|
3280
|
+
this.abortController = new AbortController();
|
|
3281
|
+
const page = this.paginationState.currentPage;
|
|
3282
|
+
const selectedValues = Array.from(this.select.selectedOptions)
|
|
3254
3283
|
.map((opt) => opt.value)
|
|
3255
3284
|
.join(",");
|
|
3256
3285
|
let payload;
|
|
3257
3286
|
if (typeof cfg.data === "function") {
|
|
3258
|
-
payload = cfg.data.bind(this.
|
|
3287
|
+
payload = cfg.data.bind(this.selectBox.Selective.find(this.selectBox.container.targetElement))(keyword, page);
|
|
3259
3288
|
if (payload && typeof payload.selectedValue === "undefined")
|
|
3260
3289
|
payload.selectedValue = selectedValues;
|
|
3261
3290
|
}
|
|
@@ -3271,27 +3300,27 @@
|
|
|
3271
3300
|
method: "POST",
|
|
3272
3301
|
body: formData,
|
|
3273
3302
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3274
|
-
signal: this.
|
|
3303
|
+
signal: this.abortController.signal,
|
|
3275
3304
|
});
|
|
3276
3305
|
}
|
|
3277
3306
|
else {
|
|
3278
3307
|
const params = new URLSearchParams(payload).toString();
|
|
3279
|
-
response = await fetch(`${cfg.url}?${params}`, { signal: this.
|
|
3308
|
+
response = await fetch(`${cfg.url}?${params}`, { signal: this.abortController.signal });
|
|
3280
3309
|
}
|
|
3281
3310
|
const data = await response.json();
|
|
3282
|
-
const result = this.
|
|
3311
|
+
const result = this.parseResponse(data);
|
|
3283
3312
|
if (result.hasPagination) {
|
|
3284
|
-
this.
|
|
3285
|
-
this.
|
|
3286
|
-
this.
|
|
3287
|
-
this.
|
|
3313
|
+
this.paginationState.isPaginationEnabled = true;
|
|
3314
|
+
this.paginationState.currentPage = result.page;
|
|
3315
|
+
this.paginationState.totalPages = result.totalPages;
|
|
3316
|
+
this.paginationState.hasMore = result.hasMore;
|
|
3288
3317
|
}
|
|
3289
3318
|
else {
|
|
3290
|
-
this.
|
|
3319
|
+
this.paginationState.isPaginationEnabled = false;
|
|
3291
3320
|
}
|
|
3292
3321
|
this.applyAjaxResult(result.items, !!cfg.keepSelected, append);
|
|
3293
|
-
this.
|
|
3294
|
-
this.
|
|
3322
|
+
this.paginationState.isLoading = false;
|
|
3323
|
+
this.popup?.hideLoading();
|
|
3295
3324
|
return {
|
|
3296
3325
|
success: true,
|
|
3297
3326
|
hasResults: result.items.length > 0,
|
|
@@ -3303,8 +3332,8 @@
|
|
|
3303
3332
|
};
|
|
3304
3333
|
}
|
|
3305
3334
|
catch (error) {
|
|
3306
|
-
this.
|
|
3307
|
-
this.
|
|
3335
|
+
this.paginationState.isLoading = false;
|
|
3336
|
+
this.popup?.hideLoading();
|
|
3308
3337
|
if (error?.name === "AbortError")
|
|
3309
3338
|
return { success: false, message: "Request aborted" };
|
|
3310
3339
|
console.error("Ajax search error:", error);
|
|
@@ -3314,7 +3343,7 @@
|
|
|
3314
3343
|
/**
|
|
3315
3344
|
* Parses various server response shapes into a normalized structure for options and groups.
|
|
3316
3345
|
*/
|
|
3317
|
-
|
|
3346
|
+
parseResponse(data) {
|
|
3318
3347
|
let items = [];
|
|
3319
3348
|
let hasPagination = false;
|
|
3320
3349
|
let page = 0;
|
|
@@ -3381,7 +3410,7 @@
|
|
|
3381
3410
|
* Applies normalized AJAX results to the underlying <select> element.
|
|
3382
3411
|
*/
|
|
3383
3412
|
applyAjaxResult(items, keepSelected, append = false) {
|
|
3384
|
-
const select = this.
|
|
3413
|
+
const select = this.select;
|
|
3385
3414
|
let oldSelected = [];
|
|
3386
3415
|
if (keepSelected)
|
|
3387
3416
|
oldSelected = Array.from(select.selectedOptions).map((o) => o.value);
|
|
@@ -3452,23 +3481,23 @@
|
|
|
3452
3481
|
* @param {HTMLSelectElement} select - The <select> element to observe.
|
|
3453
3482
|
*/
|
|
3454
3483
|
constructor(select) {
|
|
3455
|
-
this.
|
|
3456
|
-
this.
|
|
3484
|
+
this.debounceTimer = null;
|
|
3485
|
+
this.lastSnapshot = null;
|
|
3457
3486
|
this._DEBOUNCE_DELAY = 50;
|
|
3458
|
-
this.
|
|
3459
|
-
this.
|
|
3460
|
-
this.
|
|
3461
|
-
if (this.
|
|
3462
|
-
clearTimeout(this.
|
|
3463
|
-
this.
|
|
3464
|
-
this.
|
|
3487
|
+
this.select = select;
|
|
3488
|
+
this.lastSnapshot = this.createSnapshot();
|
|
3489
|
+
this.observer = new MutationObserver(() => {
|
|
3490
|
+
if (this.debounceTimer)
|
|
3491
|
+
clearTimeout(this.debounceTimer);
|
|
3492
|
+
this.debounceTimer = setTimeout(() => {
|
|
3493
|
+
this.handleChange();
|
|
3465
3494
|
}, this._DEBOUNCE_DELAY);
|
|
3466
3495
|
});
|
|
3467
3496
|
select.addEventListener("options:changed", () => {
|
|
3468
|
-
if (this.
|
|
3469
|
-
clearTimeout(this.
|
|
3470
|
-
this.
|
|
3471
|
-
this.
|
|
3497
|
+
if (this.debounceTimer)
|
|
3498
|
+
clearTimeout(this.debounceTimer);
|
|
3499
|
+
this.debounceTimer = setTimeout(() => {
|
|
3500
|
+
this.handleChange();
|
|
3472
3501
|
}, this._DEBOUNCE_DELAY);
|
|
3473
3502
|
});
|
|
3474
3503
|
}
|
|
@@ -3478,8 +3507,8 @@
|
|
|
3478
3507
|
*
|
|
3479
3508
|
* @returns {SelectSnapshot} A snapshot of the options state.
|
|
3480
3509
|
*/
|
|
3481
|
-
|
|
3482
|
-
const options = Array.from(this.
|
|
3510
|
+
createSnapshot() {
|
|
3511
|
+
const options = Array.from(this.select.options);
|
|
3483
3512
|
return {
|
|
3484
3513
|
length: options.length,
|
|
3485
3514
|
values: options.map((opt) => opt.value).join(","),
|
|
@@ -3493,28 +3522,28 @@
|
|
|
3493
3522
|
*
|
|
3494
3523
|
* @returns {boolean} True if a real change occurred, otherwise false.
|
|
3495
3524
|
*/
|
|
3496
|
-
|
|
3497
|
-
const newSnapshot = this.
|
|
3498
|
-
const changed = JSON.stringify(newSnapshot) !== JSON.stringify(this.
|
|
3525
|
+
hasRealChange() {
|
|
3526
|
+
const newSnapshot = this.createSnapshot();
|
|
3527
|
+
const changed = JSON.stringify(newSnapshot) !== JSON.stringify(this.lastSnapshot);
|
|
3499
3528
|
if (changed)
|
|
3500
|
-
this.
|
|
3529
|
+
this.lastSnapshot = newSnapshot;
|
|
3501
3530
|
return changed;
|
|
3502
3531
|
}
|
|
3503
3532
|
/**
|
|
3504
3533
|
* Handles detected changes after debouncing.
|
|
3505
3534
|
* If a real change is found, invokes the onChanged() hook with the current <select> element.
|
|
3506
3535
|
*/
|
|
3507
|
-
|
|
3508
|
-
if (!this.
|
|
3536
|
+
handleChange() {
|
|
3537
|
+
if (!this.hasRealChange())
|
|
3509
3538
|
return;
|
|
3510
|
-
this.onChanged(this.
|
|
3539
|
+
this.onChanged(this.select);
|
|
3511
3540
|
}
|
|
3512
3541
|
/**
|
|
3513
3542
|
* Starts observing the <select> element for child list mutations and attribute changes.
|
|
3514
3543
|
* Uses MutationObserver with a debounce mechanism to batch rapid updates.
|
|
3515
3544
|
*/
|
|
3516
3545
|
connect() {
|
|
3517
|
-
this.
|
|
3546
|
+
this.observer.observe(this.select, {
|
|
3518
3547
|
childList: true,
|
|
3519
3548
|
subtree: false,
|
|
3520
3549
|
attributes: true,
|
|
@@ -3536,10 +3565,10 @@
|
|
|
3536
3565
|
* Ensures no further change handling occurs after disconnecting.
|
|
3537
3566
|
*/
|
|
3538
3567
|
disconnect() {
|
|
3539
|
-
if (this.
|
|
3540
|
-
clearTimeout(this.
|
|
3541
|
-
this.
|
|
3542
|
-
this.
|
|
3568
|
+
if (this.debounceTimer)
|
|
3569
|
+
clearTimeout(this.debounceTimer);
|
|
3570
|
+
this.debounceTimer = null;
|
|
3571
|
+
this.observer.disconnect();
|
|
3543
3572
|
}
|
|
3544
3573
|
}
|
|
3545
3574
|
|
|
@@ -3554,9 +3583,9 @@
|
|
|
3554
3583
|
* @param {HTMLElement} element - The element whose dataset (data-* attributes) will be observed.
|
|
3555
3584
|
*/
|
|
3556
3585
|
constructor(element) {
|
|
3557
|
-
this.
|
|
3558
|
-
this.
|
|
3559
|
-
this.
|
|
3586
|
+
this.debounceTimer = null;
|
|
3587
|
+
this.element = element;
|
|
3588
|
+
this.observer = new MutationObserver((mutations) => {
|
|
3560
3589
|
let datasetChanged = false;
|
|
3561
3590
|
for (const mutation of mutations) {
|
|
3562
3591
|
if (mutation.type === "attributes" && mutation.attributeName?.startsWith("data-")) {
|
|
@@ -3566,14 +3595,14 @@
|
|
|
3566
3595
|
}
|
|
3567
3596
|
if (!datasetChanged)
|
|
3568
3597
|
return;
|
|
3569
|
-
if (this.
|
|
3570
|
-
clearTimeout(this.
|
|
3571
|
-
this.
|
|
3572
|
-
this.onChanged({ ...this.
|
|
3598
|
+
if (this.debounceTimer)
|
|
3599
|
+
clearTimeout(this.debounceTimer);
|
|
3600
|
+
this.debounceTimer = setTimeout(() => {
|
|
3601
|
+
this.onChanged({ ...this.element.dataset });
|
|
3573
3602
|
}, 50);
|
|
3574
3603
|
});
|
|
3575
3604
|
element.addEventListener("dataset:changed", () => {
|
|
3576
|
-
this.onChanged({ ...this.
|
|
3605
|
+
this.onChanged({ ...this.element.dataset });
|
|
3577
3606
|
});
|
|
3578
3607
|
}
|
|
3579
3608
|
/**
|
|
@@ -3581,7 +3610,7 @@
|
|
|
3581
3610
|
* Uses MutationObserver to track updates to data-* attributes.
|
|
3582
3611
|
*/
|
|
3583
3612
|
connect() {
|
|
3584
|
-
this.
|
|
3613
|
+
this.observer.observe(this.element, {
|
|
3585
3614
|
attributes: true,
|
|
3586
3615
|
attributeOldValue: true,
|
|
3587
3616
|
});
|
|
@@ -3600,10 +3629,10 @@
|
|
|
3600
3629
|
* Stops observing the element and clears any pending debounce timers.
|
|
3601
3630
|
*/
|
|
3602
3631
|
disconnect() {
|
|
3603
|
-
if (this.
|
|
3604
|
-
clearTimeout(this.
|
|
3605
|
-
this.
|
|
3606
|
-
this.
|
|
3632
|
+
if (this.debounceTimer)
|
|
3633
|
+
clearTimeout(this.debounceTimer);
|
|
3634
|
+
this.debounceTimer = null;
|
|
3635
|
+
this.observer.disconnect();
|
|
3607
3636
|
}
|
|
3608
3637
|
}
|
|
3609
3638
|
|
|
@@ -3903,10 +3932,10 @@
|
|
|
3903
3932
|
constructor(parent) {
|
|
3904
3933
|
super(parent);
|
|
3905
3934
|
this.view = null;
|
|
3906
|
-
this.
|
|
3907
|
-
this.
|
|
3908
|
-
this.
|
|
3909
|
-
this.
|
|
3935
|
+
this.config = null;
|
|
3936
|
+
this.configProxy = null;
|
|
3937
|
+
this.isRendered = false;
|
|
3938
|
+
this.setupConfigProxy();
|
|
3910
3939
|
}
|
|
3911
3940
|
/**
|
|
3912
3941
|
* Creates the internal configuration object and wraps it with a Proxy.
|
|
@@ -3914,9 +3943,9 @@
|
|
|
3914
3943
|
* applies only the necessary DOM changes for the updated property.
|
|
3915
3944
|
* No DOM mutations occur before the first render.
|
|
3916
3945
|
*/
|
|
3917
|
-
|
|
3946
|
+
setupConfigProxy() {
|
|
3918
3947
|
const self = this;
|
|
3919
|
-
this.
|
|
3948
|
+
this.config = {
|
|
3920
3949
|
isMultiple: false,
|
|
3921
3950
|
hasImage: false,
|
|
3922
3951
|
imagePosition: "right",
|
|
@@ -3926,7 +3955,7 @@
|
|
|
3926
3955
|
labelValign: "center",
|
|
3927
3956
|
labelHalign: "left",
|
|
3928
3957
|
};
|
|
3929
|
-
this.
|
|
3958
|
+
this.configProxy = new Proxy(this.config, {
|
|
3930
3959
|
set(target, prop, value) {
|
|
3931
3960
|
if (typeof prop !== "string")
|
|
3932
3961
|
return true;
|
|
@@ -3934,8 +3963,8 @@
|
|
|
3934
3963
|
const oldValue = target[key];
|
|
3935
3964
|
if (oldValue !== value) {
|
|
3936
3965
|
target[key] = value;
|
|
3937
|
-
if (self.
|
|
3938
|
-
self.
|
|
3966
|
+
if (self.isRendered) {
|
|
3967
|
+
self.applyPartialChange(key, value, oldValue);
|
|
3939
3968
|
}
|
|
3940
3969
|
}
|
|
3941
3970
|
return true;
|
|
@@ -3948,7 +3977,7 @@
|
|
|
3948
3977
|
* @returns {boolean} True if multiple selection is enabled; otherwise false.
|
|
3949
3978
|
*/
|
|
3950
3979
|
get isMultiple() {
|
|
3951
|
-
return this.
|
|
3980
|
+
return this.config.isMultiple;
|
|
3952
3981
|
}
|
|
3953
3982
|
/**
|
|
3954
3983
|
* Enables or disables multiple selection mode.
|
|
@@ -3957,7 +3986,7 @@
|
|
|
3957
3986
|
* @param {boolean} value - True to enable multiple selection; false for single selection.
|
|
3958
3987
|
*/
|
|
3959
3988
|
set isMultiple(value) {
|
|
3960
|
-
this.
|
|
3989
|
+
this.configProxy.isMultiple = !!value;
|
|
3961
3990
|
}
|
|
3962
3991
|
/**
|
|
3963
3992
|
* Indicates whether the option includes an image block alongside the label.
|
|
@@ -3965,7 +3994,7 @@
|
|
|
3965
3994
|
* @returns {boolean} True if an image is displayed; otherwise false.
|
|
3966
3995
|
*/
|
|
3967
3996
|
get hasImage() {
|
|
3968
|
-
return this.
|
|
3997
|
+
return this.config.hasImage;
|
|
3969
3998
|
}
|
|
3970
3999
|
/**
|
|
3971
4000
|
* Shows or hides the image block for the option.
|
|
@@ -3974,7 +4003,7 @@
|
|
|
3974
4003
|
* @param {boolean} value - True to show the image; false to hide it.
|
|
3975
4004
|
*/
|
|
3976
4005
|
set hasImage(value) {
|
|
3977
|
-
this.
|
|
4006
|
+
this.configProxy.hasImage = !!value;
|
|
3978
4007
|
}
|
|
3979
4008
|
/**
|
|
3980
4009
|
* Provides reactive access to the entire option configuration via a Proxy.
|
|
@@ -3983,7 +4012,7 @@
|
|
|
3983
4012
|
* @returns {object} The proxied configuration object.
|
|
3984
4013
|
*/
|
|
3985
4014
|
get optionConfig() {
|
|
3986
|
-
return this.
|
|
4015
|
+
return this.configProxy;
|
|
3987
4016
|
}
|
|
3988
4017
|
/**
|
|
3989
4018
|
* Applies a set of configuration changes in batch.
|
|
@@ -3991,23 +4020,23 @@
|
|
|
3991
4020
|
* When rendered, each changed property triggers a targeted DOM update via the proxy.
|
|
3992
4021
|
*/
|
|
3993
4022
|
set optionConfig(config) {
|
|
3994
|
-
if (!config || !this.
|
|
4023
|
+
if (!config || !this.configProxy || !this.config)
|
|
3995
4024
|
return;
|
|
3996
4025
|
const changes = {};
|
|
3997
|
-
if (config.imageWidth !== undefined && config.imageWidth !== this.
|
|
4026
|
+
if (config.imageWidth !== undefined && config.imageWidth !== this.config.imageWidth)
|
|
3998
4027
|
changes.imageWidth = config.imageWidth;
|
|
3999
|
-
if (config.imageHeight !== undefined && config.imageHeight !== this.
|
|
4028
|
+
if (config.imageHeight !== undefined && config.imageHeight !== this.config.imageHeight)
|
|
4000
4029
|
changes.imageHeight = config.imageHeight;
|
|
4001
|
-
if (config.imageBorderRadius !== undefined && config.imageBorderRadius !== this.
|
|
4030
|
+
if (config.imageBorderRadius !== undefined && config.imageBorderRadius !== this.config.imageBorderRadius)
|
|
4002
4031
|
changes.imageBorderRadius = config.imageBorderRadius;
|
|
4003
|
-
if (config.imagePosition !== undefined && config.imagePosition !== this.
|
|
4032
|
+
if (config.imagePosition !== undefined && config.imagePosition !== this.config.imagePosition)
|
|
4004
4033
|
changes.imagePosition = config.imagePosition;
|
|
4005
|
-
if (config.labelValign !== undefined && config.labelValign !== this.
|
|
4034
|
+
if (config.labelValign !== undefined && config.labelValign !== this.config.labelValign)
|
|
4006
4035
|
changes.labelValign = config.labelValign;
|
|
4007
|
-
if (config.labelHalign !== undefined && config.labelHalign !== this.
|
|
4036
|
+
if (config.labelHalign !== undefined && config.labelHalign !== this.config.labelHalign)
|
|
4008
4037
|
changes.labelHalign = config.labelHalign;
|
|
4009
4038
|
if (Object.keys(changes).length > 0)
|
|
4010
|
-
Object.assign(this.
|
|
4039
|
+
Object.assign(this.configProxy, changes);
|
|
4011
4040
|
}
|
|
4012
4041
|
/**
|
|
4013
4042
|
* Renders the option view into the parent element.
|
|
@@ -4019,30 +4048,30 @@
|
|
|
4019
4048
|
const viewClass = ["selective-ui-option-view"];
|
|
4020
4049
|
const opt_id = Libs.randomString(7);
|
|
4021
4050
|
const inputID = `option_${opt_id}`;
|
|
4022
|
-
if (this.
|
|
4051
|
+
if (this.config.isMultiple)
|
|
4023
4052
|
viewClass.push("multiple");
|
|
4024
|
-
if (this.
|
|
4053
|
+
if (this.config.hasImage) {
|
|
4025
4054
|
viewClass.push("has-image");
|
|
4026
|
-
viewClass.push(`image-${this.
|
|
4055
|
+
viewClass.push(`image-${this.config.imagePosition}`);
|
|
4027
4056
|
}
|
|
4028
4057
|
const childStructure = {
|
|
4029
4058
|
OptionInput: {
|
|
4030
4059
|
tag: {
|
|
4031
4060
|
node: "input",
|
|
4032
|
-
type: this.
|
|
4061
|
+
type: this.config.isMultiple ? "checkbox" : "radio",
|
|
4033
4062
|
classList: "allow-choice",
|
|
4034
4063
|
id: inputID,
|
|
4035
4064
|
},
|
|
4036
4065
|
},
|
|
4037
|
-
...(this.
|
|
4066
|
+
...(this.config.hasImage && {
|
|
4038
4067
|
OptionImage: {
|
|
4039
4068
|
tag: {
|
|
4040
4069
|
node: "img",
|
|
4041
4070
|
classList: "option-image",
|
|
4042
4071
|
style: {
|
|
4043
|
-
width: this.
|
|
4044
|
-
height: this.
|
|
4045
|
-
borderRadius: this.
|
|
4072
|
+
width: this.config.imageWidth,
|
|
4073
|
+
height: this.config.imageHeight,
|
|
4074
|
+
borderRadius: this.config.imageBorderRadius,
|
|
4046
4075
|
},
|
|
4047
4076
|
},
|
|
4048
4077
|
},
|
|
@@ -4052,8 +4081,8 @@
|
|
|
4052
4081
|
node: "label",
|
|
4053
4082
|
htmlFor: inputID,
|
|
4054
4083
|
classList: [
|
|
4055
|
-
`align-vertical-${this.
|
|
4056
|
-
`align-horizontal-${this.
|
|
4084
|
+
`align-vertical-${this.config.labelValign}`,
|
|
4085
|
+
`align-horizontal-${this.config.labelHalign}`,
|
|
4057
4086
|
],
|
|
4058
4087
|
},
|
|
4059
4088
|
child: {
|
|
@@ -4075,13 +4104,13 @@
|
|
|
4075
4104
|
},
|
|
4076
4105
|
});
|
|
4077
4106
|
this.parent.appendChild(this.view.view);
|
|
4078
|
-
this.
|
|
4107
|
+
this.isRendered = true;
|
|
4079
4108
|
}
|
|
4080
4109
|
/**
|
|
4081
4110
|
* Applies a targeted DOM update for a single configuration property change.
|
|
4082
4111
|
* Safely updates classes, attributes, styles, and child elements without re-rendering the whole view.
|
|
4083
4112
|
*/
|
|
4084
|
-
|
|
4113
|
+
applyPartialChange(prop, newValue, oldValue) {
|
|
4085
4114
|
const v = this.view;
|
|
4086
4115
|
if (!v || !v.view)
|
|
4087
4116
|
return;
|
|
@@ -4101,8 +4130,8 @@
|
|
|
4101
4130
|
const val = !!newValue;
|
|
4102
4131
|
root.classList.toggle("has-image", val);
|
|
4103
4132
|
if (val) {
|
|
4104
|
-
root.classList.add(`image-${this.
|
|
4105
|
-
this.
|
|
4133
|
+
root.classList.add(`image-${this.config.imagePosition}`);
|
|
4134
|
+
this.createImage();
|
|
4106
4135
|
}
|
|
4107
4136
|
else {
|
|
4108
4137
|
root.className = root.className.replace(/image-(top|right|bottom|left)/g, "").trim();
|
|
@@ -4115,7 +4144,7 @@
|
|
|
4115
4144
|
break;
|
|
4116
4145
|
}
|
|
4117
4146
|
case "imagePosition": {
|
|
4118
|
-
if (this.
|
|
4147
|
+
if (this.config.hasImage) {
|
|
4119
4148
|
root.className = root.className.replace(/image-(top|right|bottom|left)/g, "").trim();
|
|
4120
4149
|
root.classList.add(`image-${String(newValue)}`);
|
|
4121
4150
|
}
|
|
@@ -4138,7 +4167,7 @@
|
|
|
4138
4167
|
case "labelValign":
|
|
4139
4168
|
case "labelHalign": {
|
|
4140
4169
|
if (label) {
|
|
4141
|
-
const newClass = `align-vertical-${this.
|
|
4170
|
+
const newClass = `align-vertical-${this.config.labelValign} align-horizontal-${this.config.labelHalign}`;
|
|
4142
4171
|
if (label.className !== newClass)
|
|
4143
4172
|
label.className = newClass;
|
|
4144
4173
|
}
|
|
@@ -4152,7 +4181,7 @@
|
|
|
4152
4181
|
* The image receives configured styles (width, height, borderRadius) and is placed
|
|
4153
4182
|
* before the label if present; otherwise appended to the root. Updates `v.tags.OptionImage`.
|
|
4154
4183
|
*/
|
|
4155
|
-
|
|
4184
|
+
createImage() {
|
|
4156
4185
|
const v = this.view;
|
|
4157
4186
|
if (!v || !v.view)
|
|
4158
4187
|
return;
|
|
@@ -4163,9 +4192,9 @@
|
|
|
4163
4192
|
const label = v.tags?.OptionLabel;
|
|
4164
4193
|
const image = document.createElement("img");
|
|
4165
4194
|
image.className = "option-image";
|
|
4166
|
-
image.style.width = this.
|
|
4167
|
-
image.style.height = this.
|
|
4168
|
-
image.style.borderRadius = this.
|
|
4195
|
+
image.style.width = this.config.imageWidth;
|
|
4196
|
+
image.style.height = this.config.imageHeight;
|
|
4197
|
+
image.style.borderRadius = this.config.imageBorderRadius;
|
|
4169
4198
|
if (label && label.parentElement)
|
|
4170
4199
|
root.insertBefore(image, label);
|
|
4171
4200
|
else
|
|
@@ -4181,16 +4210,16 @@
|
|
|
4181
4210
|
constructor(items = []) {
|
|
4182
4211
|
super(items);
|
|
4183
4212
|
this.isMultiple = false;
|
|
4184
|
-
this.
|
|
4185
|
-
this.
|
|
4186
|
-
this.
|
|
4213
|
+
this.visibilityChangedCallbacks = [];
|
|
4214
|
+
this.currentHighlightIndex = -1;
|
|
4215
|
+
this.selectedItemSingle = null;
|
|
4187
4216
|
this.groups = [];
|
|
4188
4217
|
this.flatOptions = [];
|
|
4189
|
-
this.
|
|
4218
|
+
this.buildFlatStructure();
|
|
4190
4219
|
Libs.callbackScheduler.on(`sche_vis_${this.adapterKey}`, () => {
|
|
4191
4220
|
const visibleCount = this.flatOptions.filter((item) => item.visible).length;
|
|
4192
4221
|
const totalCount = this.flatOptions.length;
|
|
4193
|
-
this.
|
|
4222
|
+
this.visibilityChangedCallbacks.forEach((callback) => {
|
|
4194
4223
|
callback({
|
|
4195
4224
|
visibleCount,
|
|
4196
4225
|
totalCount,
|
|
@@ -4204,7 +4233,7 @@
|
|
|
4204
4233
|
/**
|
|
4205
4234
|
* Build flat list of all options for navigation
|
|
4206
4235
|
*/
|
|
4207
|
-
|
|
4236
|
+
buildFlatStructure() {
|
|
4208
4237
|
this.flatOptions = [];
|
|
4209
4238
|
this.groups = [];
|
|
4210
4239
|
this.items.forEach((item) => {
|
|
@@ -4240,10 +4269,10 @@
|
|
|
4240
4269
|
onViewHolder(item, viewer, position) {
|
|
4241
4270
|
item.position = position;
|
|
4242
4271
|
if (item instanceof GroupModel) {
|
|
4243
|
-
this.
|
|
4272
|
+
this.handleGroupView(item, viewer, position);
|
|
4244
4273
|
}
|
|
4245
4274
|
else if (item instanceof OptionModel) {
|
|
4246
|
-
this.
|
|
4275
|
+
this.handleOptionView(item, viewer, position);
|
|
4247
4276
|
}
|
|
4248
4277
|
item.isInit = true;
|
|
4249
4278
|
}
|
|
@@ -4255,7 +4284,7 @@
|
|
|
4255
4284
|
* @param {GroupView} groupView - The view instance that renders the group in the UI.
|
|
4256
4285
|
* @param {number} position - The position (index) of the group within a list.
|
|
4257
4286
|
*/
|
|
4258
|
-
|
|
4287
|
+
handleGroupView(groupModel, groupView, position) {
|
|
4259
4288
|
super.onViewHolder(groupModel, groupView, position);
|
|
4260
4289
|
groupModel.view = groupView;
|
|
4261
4290
|
const header = groupView.view.tags.GroupHeader;
|
|
@@ -4280,7 +4309,7 @@
|
|
|
4280
4309
|
if (!optionModel.isInit || !optionViewer) {
|
|
4281
4310
|
optionViewer = new OptionView(itemsContainer);
|
|
4282
4311
|
}
|
|
4283
|
-
this.
|
|
4312
|
+
this.handleOptionView(optionModel, optionViewer, idx);
|
|
4284
4313
|
optionModel.isInit = true;
|
|
4285
4314
|
});
|
|
4286
4315
|
groupView.setCollapsed(groupModel.collapsed);
|
|
@@ -4294,7 +4323,7 @@
|
|
|
4294
4323
|
* @param {OptionView} optionViewer - The view instance that renders the option in the UI.
|
|
4295
4324
|
* @param {number} position - The index of this option within its group's item list.
|
|
4296
4325
|
*/
|
|
4297
|
-
|
|
4326
|
+
handleOptionView(optionModel, optionViewer, position) {
|
|
4298
4327
|
optionViewer.isMultiple = this.isMultiple;
|
|
4299
4328
|
optionViewer.hasImage = optionModel.hasImage;
|
|
4300
4329
|
optionViewer.optionConfig = {
|
|
@@ -4334,8 +4363,8 @@
|
|
|
4334
4363
|
else if (optionModel.selected !== true) {
|
|
4335
4364
|
this.changingProp("select");
|
|
4336
4365
|
setTimeout(() => {
|
|
4337
|
-
if (this.
|
|
4338
|
-
this.
|
|
4366
|
+
if (this.selectedItemSingle)
|
|
4367
|
+
this.selectedItemSingle.selected = false;
|
|
4339
4368
|
optionModel.selected = true;
|
|
4340
4369
|
}, 5);
|
|
4341
4370
|
}
|
|
@@ -4351,16 +4380,16 @@
|
|
|
4351
4380
|
});
|
|
4352
4381
|
optionModel.onInternalSelected((_evtToken, _el, selected) => {
|
|
4353
4382
|
if (selected)
|
|
4354
|
-
this.
|
|
4383
|
+
this.selectedItemSingle = optionModel;
|
|
4355
4384
|
this.changeProp("selected_internal");
|
|
4356
4385
|
});
|
|
4357
4386
|
optionModel.onVisibilityChanged((_evtToken, model, _visible) => {
|
|
4358
4387
|
model.group?.updateVisibility();
|
|
4359
|
-
this.
|
|
4388
|
+
this.notifyVisibilityChanged();
|
|
4360
4389
|
});
|
|
4361
4390
|
}
|
|
4362
4391
|
if (optionModel.selected) {
|
|
4363
|
-
this.
|
|
4392
|
+
this.selectedItemSingle = optionModel;
|
|
4364
4393
|
optionModel.selectedNonTrigger = true;
|
|
4365
4394
|
}
|
|
4366
4395
|
}
|
|
@@ -4372,7 +4401,7 @@
|
|
|
4372
4401
|
setItems(items) {
|
|
4373
4402
|
this.changingProp("items", items);
|
|
4374
4403
|
this.items = items;
|
|
4375
|
-
this.
|
|
4404
|
+
this.buildFlatStructure();
|
|
4376
4405
|
this.changeProp("items", items);
|
|
4377
4406
|
}
|
|
4378
4407
|
/**
|
|
@@ -4391,7 +4420,7 @@
|
|
|
4391
4420
|
*/
|
|
4392
4421
|
updateData(items) {
|
|
4393
4422
|
this.items = items;
|
|
4394
|
-
this.
|
|
4423
|
+
this.buildFlatStructure();
|
|
4395
4424
|
}
|
|
4396
4425
|
/**
|
|
4397
4426
|
* Returns all option items that are currently selected.
|
|
@@ -4428,13 +4457,13 @@
|
|
|
4428
4457
|
* - Function to invoke when visibility stats change.
|
|
4429
4458
|
*/
|
|
4430
4459
|
onVisibilityChanged(callback) {
|
|
4431
|
-
this.
|
|
4460
|
+
this.visibilityChangedCallbacks.push(callback);
|
|
4432
4461
|
}
|
|
4433
4462
|
/**
|
|
4434
4463
|
* Notifies all registered visibility-change callbacks with up-to-date statistics.
|
|
4435
4464
|
* Computes visible and total counts, then emits aggregated state.
|
|
4436
4465
|
*/
|
|
4437
|
-
|
|
4466
|
+
notifyVisibilityChanged() {
|
|
4438
4467
|
Libs.callbackScheduler.run(`sche_vis_${this.adapterKey}`);
|
|
4439
4468
|
}
|
|
4440
4469
|
/**
|
|
@@ -4466,7 +4495,7 @@
|
|
|
4466
4495
|
const visibleOptions = this.flatOptions.filter((opt) => opt.visible);
|
|
4467
4496
|
if (visibleOptions.length === 0)
|
|
4468
4497
|
return;
|
|
4469
|
-
let currentVisibleIndex = visibleOptions.findIndex((opt) => opt === this.flatOptions[this.
|
|
4498
|
+
let currentVisibleIndex = visibleOptions.findIndex((opt) => opt === this.flatOptions[this.currentHighlightIndex]);
|
|
4470
4499
|
if (currentVisibleIndex === -1)
|
|
4471
4500
|
currentVisibleIndex = -1;
|
|
4472
4501
|
let nextVisibleIndex = currentVisibleIndex + direction;
|
|
@@ -4483,8 +4512,8 @@
|
|
|
4483
4512
|
* No-op if nothing is highlighted or the highlighted item is not visible.
|
|
4484
4513
|
*/
|
|
4485
4514
|
selectHighlighted() {
|
|
4486
|
-
if (this.
|
|
4487
|
-
const item = this.flatOptions[this.
|
|
4515
|
+
if (this.currentHighlightIndex > -1 && this.flatOptions[this.currentHighlightIndex]) {
|
|
4516
|
+
const item = this.flatOptions[this.currentHighlightIndex];
|
|
4488
4517
|
if (item.visible) {
|
|
4489
4518
|
const viewEl = item.view?.getView?.();
|
|
4490
4519
|
if (viewEl)
|
|
@@ -4511,15 +4540,15 @@
|
|
|
4511
4540
|
else {
|
|
4512
4541
|
index = 0;
|
|
4513
4542
|
}
|
|
4514
|
-
if (this.
|
|
4515
|
-
this.flatOptions[this.
|
|
4543
|
+
if (this.currentHighlightIndex > -1 && this.flatOptions[this.currentHighlightIndex]) {
|
|
4544
|
+
this.flatOptions[this.currentHighlightIndex].highlighted = false;
|
|
4516
4545
|
}
|
|
4517
4546
|
for (let i = index; i < this.flatOptions.length; i++) {
|
|
4518
4547
|
const item = this.flatOptions[i];
|
|
4519
4548
|
if (!item?.visible)
|
|
4520
4549
|
continue;
|
|
4521
4550
|
item.highlighted = true;
|
|
4522
|
-
this.
|
|
4551
|
+
this.currentHighlightIndex = i;
|
|
4523
4552
|
if (isScrollToView) {
|
|
4524
4553
|
const el = item.view?.getView?.();
|
|
4525
4554
|
if (el) {
|
|
@@ -4628,15 +4657,15 @@
|
|
|
4628
4657
|
this.firstMeasured = false;
|
|
4629
4658
|
this.start = 0;
|
|
4630
4659
|
this.end = -1;
|
|
4631
|
-
this.
|
|
4632
|
-
this.
|
|
4633
|
-
this.
|
|
4634
|
-
this.
|
|
4635
|
-
this.
|
|
4636
|
-
this.
|
|
4637
|
-
this.
|
|
4638
|
-
this.
|
|
4639
|
-
this.
|
|
4660
|
+
this.rafId = null;
|
|
4661
|
+
this.measureRaf = null;
|
|
4662
|
+
this.updating = false;
|
|
4663
|
+
this.suppressResize = false;
|
|
4664
|
+
this.lastRenderCount = 0;
|
|
4665
|
+
this.suspended = false;
|
|
4666
|
+
this.resumeResizeAfter = false;
|
|
4667
|
+
this.stickyCacheTick = 0;
|
|
4668
|
+
this.stickyCacheVal = 0;
|
|
4640
4669
|
this.measuredSum = 0;
|
|
4641
4670
|
this.measuredCount = 0;
|
|
4642
4671
|
}
|
|
@@ -4669,8 +4698,8 @@
|
|
|
4669
4698
|
?? this.viewElement.parentElement;
|
|
4670
4699
|
if (!this.scrollEl)
|
|
4671
4700
|
throw new Error("VirtualRecyclerView: scrollEl not found");
|
|
4672
|
-
this.
|
|
4673
|
-
this.scrollEl.addEventListener("scroll", this.
|
|
4701
|
+
this.boundOnScroll = this.onScroll.bind(this);
|
|
4702
|
+
this.scrollEl.addEventListener("scroll", this.boundOnScroll, { passive: true });
|
|
4674
4703
|
this.refresh(false);
|
|
4675
4704
|
this.attachResizeObserverOnce();
|
|
4676
4705
|
adapter?.onVisibilityChanged?.(() => this.refreshItem());
|
|
@@ -4680,14 +4709,14 @@
|
|
|
4680
4709
|
* Cancels pending frames and disconnects observers.
|
|
4681
4710
|
*/
|
|
4682
4711
|
suspend() {
|
|
4683
|
-
this.
|
|
4712
|
+
this.suspended = true;
|
|
4684
4713
|
this.cancelFrames();
|
|
4685
|
-
if (this.scrollEl && this.
|
|
4686
|
-
this.scrollEl.removeEventListener("scroll", this.
|
|
4714
|
+
if (this.scrollEl && this.boundOnScroll) {
|
|
4715
|
+
this.scrollEl.removeEventListener("scroll", this.boundOnScroll);
|
|
4687
4716
|
}
|
|
4688
4717
|
if (this.resizeObs) {
|
|
4689
4718
|
this.resizeObs.disconnect();
|
|
4690
|
-
this.
|
|
4719
|
+
this.resumeResizeAfter = true;
|
|
4691
4720
|
}
|
|
4692
4721
|
}
|
|
4693
4722
|
/**
|
|
@@ -4695,13 +4724,13 @@
|
|
|
4695
4724
|
* Re-attaches listeners and schedules window update.
|
|
4696
4725
|
*/
|
|
4697
4726
|
resume() {
|
|
4698
|
-
this.
|
|
4699
|
-
if (this.scrollEl && this.
|
|
4700
|
-
this.scrollEl.addEventListener("scroll", this.
|
|
4727
|
+
this.suspended = false;
|
|
4728
|
+
if (this.scrollEl && this.boundOnScroll) {
|
|
4729
|
+
this.scrollEl.addEventListener("scroll", this.boundOnScroll, { passive: true });
|
|
4701
4730
|
}
|
|
4702
|
-
if (this.
|
|
4731
|
+
if (this.resumeResizeAfter) {
|
|
4703
4732
|
this.attachResizeObserverOnce();
|
|
4704
|
-
this.
|
|
4733
|
+
this.resumeResizeAfter = false;
|
|
4705
4734
|
}
|
|
4706
4735
|
this.scheduleUpdateWindow();
|
|
4707
4736
|
}
|
|
@@ -4717,7 +4746,7 @@
|
|
|
4717
4746
|
if (!isUpdate)
|
|
4718
4747
|
this.refreshItem();
|
|
4719
4748
|
const count = this.adapter.itemCount();
|
|
4720
|
-
this.
|
|
4749
|
+
this.lastRenderCount = count;
|
|
4721
4750
|
if (count === 0) {
|
|
4722
4751
|
this.resetState();
|
|
4723
4752
|
return;
|
|
@@ -4759,8 +4788,8 @@
|
|
|
4759
4788
|
*/
|
|
4760
4789
|
dispose() {
|
|
4761
4790
|
this.cancelFrames();
|
|
4762
|
-
if (this.scrollEl && this.
|
|
4763
|
-
this.scrollEl.removeEventListener("scroll", this.
|
|
4791
|
+
if (this.scrollEl && this.boundOnScroll) {
|
|
4792
|
+
this.scrollEl.removeEventListener("scroll", this.boundOnScroll);
|
|
4764
4793
|
}
|
|
4765
4794
|
this.resizeObs?.disconnect();
|
|
4766
4795
|
this.created.forEach(el => el.remove());
|
|
@@ -4788,13 +4817,13 @@
|
|
|
4788
4817
|
}
|
|
4789
4818
|
/** Cancels all pending animation frames. */
|
|
4790
4819
|
cancelFrames() {
|
|
4791
|
-
if (this.
|
|
4792
|
-
cancelAnimationFrame(this.
|
|
4793
|
-
this.
|
|
4820
|
+
if (this.rafId != null) {
|
|
4821
|
+
cancelAnimationFrame(this.rafId);
|
|
4822
|
+
this.rafId = null;
|
|
4794
4823
|
}
|
|
4795
|
-
if (this.
|
|
4796
|
-
cancelAnimationFrame(this.
|
|
4797
|
-
this.
|
|
4824
|
+
if (this.measureRaf != null) {
|
|
4825
|
+
cancelAnimationFrame(this.measureRaf);
|
|
4826
|
+
this.measureRaf = null;
|
|
4798
4827
|
}
|
|
4799
4828
|
}
|
|
4800
4829
|
/** Resets all internal state: DOM, caches, measurements. */
|
|
@@ -4880,19 +4909,19 @@
|
|
|
4880
4909
|
*/
|
|
4881
4910
|
stickyTopHeight() {
|
|
4882
4911
|
const now = performance.now();
|
|
4883
|
-
if (now - this.
|
|
4884
|
-
return this.
|
|
4912
|
+
if (now - this.stickyCacheTick < 16)
|
|
4913
|
+
return this.stickyCacheVal;
|
|
4885
4914
|
const sticky = this.scrollEl.querySelector(".selective-ui-option-handle:not(.hide)");
|
|
4886
|
-
this.
|
|
4887
|
-
this.
|
|
4888
|
-
return this.
|
|
4915
|
+
this.stickyCacheVal = sticky?.offsetHeight ?? 0;
|
|
4916
|
+
this.stickyCacheTick = now;
|
|
4917
|
+
return this.stickyCacheVal;
|
|
4889
4918
|
}
|
|
4890
4919
|
/** Schedules window update on next frame if not already scheduled. */
|
|
4891
4920
|
scheduleUpdateWindow() {
|
|
4892
|
-
if (this.
|
|
4921
|
+
if (this.rafId != null || this.suspended)
|
|
4893
4922
|
return;
|
|
4894
|
-
this.
|
|
4895
|
-
this.
|
|
4923
|
+
this.rafId = requestAnimationFrame(() => {
|
|
4924
|
+
this.rafId = null;
|
|
4896
4925
|
this.updateWindowInternal();
|
|
4897
4926
|
});
|
|
4898
4927
|
}
|
|
@@ -5014,10 +5043,10 @@
|
|
|
5014
5043
|
if (this.resizeObs)
|
|
5015
5044
|
return;
|
|
5016
5045
|
this.resizeObs = new ResizeObserver(() => {
|
|
5017
|
-
if (this.
|
|
5046
|
+
if (this.suppressResize || this.suspended || !this.adapter || this.measureRaf != null)
|
|
5018
5047
|
return;
|
|
5019
|
-
this.
|
|
5020
|
-
this.
|
|
5048
|
+
this.measureRaf = requestAnimationFrame(() => {
|
|
5049
|
+
this.measureRaf = null;
|
|
5021
5050
|
this.measureVisibleAndUpdate();
|
|
5022
5051
|
});
|
|
5023
5052
|
});
|
|
@@ -5067,17 +5096,17 @@
|
|
|
5067
5096
|
* 7. Adjusts scroll position to maintain anchor item position
|
|
5068
5097
|
*/
|
|
5069
5098
|
updateWindowInternal() {
|
|
5070
|
-
if (this.
|
|
5099
|
+
if (this.updating || this.suspended)
|
|
5071
5100
|
return;
|
|
5072
|
-
this.
|
|
5101
|
+
this.updating = true;
|
|
5073
5102
|
try {
|
|
5074
5103
|
if (!this.adapter)
|
|
5075
5104
|
return;
|
|
5076
5105
|
const count = this.adapter.itemCount();
|
|
5077
5106
|
if (count <= 0)
|
|
5078
5107
|
return;
|
|
5079
|
-
if (this.
|
|
5080
|
-
this.
|
|
5108
|
+
if (this.lastRenderCount !== count) {
|
|
5109
|
+
this.lastRenderCount = count;
|
|
5081
5110
|
this.heightCache.length = count;
|
|
5082
5111
|
this.rebuildFenwick(count);
|
|
5083
5112
|
}
|
|
@@ -5101,7 +5130,7 @@
|
|
|
5101
5130
|
return;
|
|
5102
5131
|
this.start = startIndex;
|
|
5103
5132
|
this.end = endIndex;
|
|
5104
|
-
this.
|
|
5133
|
+
this.suppressResize = true;
|
|
5105
5134
|
try {
|
|
5106
5135
|
this.mountRange(this.start, this.end);
|
|
5107
5136
|
this.unmountOutside(this.start, this.end);
|
|
@@ -5115,7 +5144,7 @@
|
|
|
5115
5144
|
this.PadBottom.style.height = `${bottomPx}px`;
|
|
5116
5145
|
}
|
|
5117
5146
|
finally {
|
|
5118
|
-
this.
|
|
5147
|
+
this.suppressResize = false;
|
|
5119
5148
|
}
|
|
5120
5149
|
const anchorTopNew = this.offsetTopOf(anchorIndex);
|
|
5121
5150
|
const targetScroll = this.containerTopInScroll() + anchorTopNew - anchorDelta;
|
|
@@ -5126,7 +5155,7 @@
|
|
|
5126
5155
|
}
|
|
5127
5156
|
}
|
|
5128
5157
|
finally {
|
|
5129
|
-
this.
|
|
5158
|
+
this.updating = false;
|
|
5130
5159
|
}
|
|
5131
5160
|
}
|
|
5132
5161
|
/** Mounts all items in inclusive range [start..end]. */
|
|
@@ -5418,6 +5447,7 @@
|
|
|
5418
5447
|
.search(keyword)
|
|
5419
5448
|
.then((result) => {
|
|
5420
5449
|
clearTimeout(hightlightTimer);
|
|
5450
|
+
Libs.callbackScheduler.clear(`sche_vis_proxy_${optionAdapter.adapterKey}`);
|
|
5421
5451
|
Libs.callbackScheduler.on(`sche_vis_proxy_${optionAdapter.adapterKey}`, () => {
|
|
5422
5452
|
container.popup?.triggerResize?.();
|
|
5423
5453
|
if (result?.hasResults) {
|
|
@@ -5875,9 +5905,9 @@
|
|
|
5875
5905
|
*/
|
|
5876
5906
|
class ElementAdditionObserver {
|
|
5877
5907
|
constructor() {
|
|
5878
|
-
this.
|
|
5879
|
-
this.
|
|
5880
|
-
this.
|
|
5908
|
+
this.isActive = false;
|
|
5909
|
+
this.observer = null;
|
|
5910
|
+
this.actions = [];
|
|
5881
5911
|
}
|
|
5882
5912
|
/**
|
|
5883
5913
|
* Registers a callback to be invoked whenever a matching element is detected being added to the DOM.
|
|
@@ -5885,13 +5915,13 @@
|
|
|
5885
5915
|
* @param {(el: T) => void} action - Function executed with the newly added element.
|
|
5886
5916
|
*/
|
|
5887
5917
|
onDetect(action) {
|
|
5888
|
-
this.
|
|
5918
|
+
this.actions.push(action);
|
|
5889
5919
|
}
|
|
5890
5920
|
/**
|
|
5891
5921
|
* Clears all previously registered detection callbacks.
|
|
5892
5922
|
*/
|
|
5893
5923
|
clearDetect() {
|
|
5894
|
-
this.
|
|
5924
|
+
this.actions = [];
|
|
5895
5925
|
}
|
|
5896
5926
|
/**
|
|
5897
5927
|
* Starts observing the document for additions of elements matching the given tag.
|
|
@@ -5900,26 +5930,26 @@
|
|
|
5900
5930
|
* @param {string} tag - The tag name to watch for (e.g., "select", "div").
|
|
5901
5931
|
*/
|
|
5902
5932
|
start(tag) {
|
|
5903
|
-
if (this.
|
|
5933
|
+
if (this.isActive)
|
|
5904
5934
|
return;
|
|
5905
|
-
this.
|
|
5935
|
+
this.isActive = true;
|
|
5906
5936
|
const upperTag = tag.toUpperCase();
|
|
5907
5937
|
const lowerTag = tag.toLowerCase();
|
|
5908
|
-
this.
|
|
5938
|
+
this.observer = new MutationObserver((mutations) => {
|
|
5909
5939
|
for (const mutation of mutations) {
|
|
5910
5940
|
mutation.addedNodes.forEach((node) => {
|
|
5911
5941
|
if (node.nodeType !== 1)
|
|
5912
5942
|
return;
|
|
5913
5943
|
const subnode = node;
|
|
5914
5944
|
if (subnode.tagName === upperTag) {
|
|
5915
|
-
this.
|
|
5945
|
+
this.handle(subnode);
|
|
5916
5946
|
}
|
|
5917
5947
|
const matches = subnode.querySelectorAll(lowerTag);
|
|
5918
|
-
matches.forEach((el) => this.
|
|
5948
|
+
matches.forEach((el) => this.handle(el));
|
|
5919
5949
|
});
|
|
5920
5950
|
}
|
|
5921
5951
|
});
|
|
5922
|
-
this.
|
|
5952
|
+
this.observer.observe(document.body, {
|
|
5923
5953
|
childList: true,
|
|
5924
5954
|
subtree: true,
|
|
5925
5955
|
});
|
|
@@ -5929,19 +5959,19 @@
|
|
|
5929
5959
|
* No-ops if the observer is not active.
|
|
5930
5960
|
*/
|
|
5931
5961
|
stop() {
|
|
5932
|
-
if (!this.
|
|
5962
|
+
if (!this.isActive)
|
|
5933
5963
|
return;
|
|
5934
|
-
this.
|
|
5935
|
-
this.
|
|
5936
|
-
this.
|
|
5964
|
+
this.isActive = false;
|
|
5965
|
+
this.observer?.disconnect();
|
|
5966
|
+
this.observer = null;
|
|
5937
5967
|
}
|
|
5938
5968
|
/**
|
|
5939
5969
|
* Internal handler that invokes all registered detection callbacks for the provided element.
|
|
5940
5970
|
*
|
|
5941
5971
|
* @param {T} element - The element that was detected as added to the DOM.
|
|
5942
5972
|
*/
|
|
5943
|
-
|
|
5944
|
-
this.
|
|
5973
|
+
handle(element) {
|
|
5974
|
+
this.actions.forEach((action) => action(element));
|
|
5945
5975
|
}
|
|
5946
5976
|
}
|
|
5947
5977
|
|
|
@@ -6256,7 +6286,7 @@
|
|
|
6256
6286
|
if (typeof globalThis.GLOBAL_SEUI == "undefined") {
|
|
6257
6287
|
const SECLASS = new Selective();
|
|
6258
6288
|
globalThis.GLOBAL_SEUI = {
|
|
6259
|
-
version: "1.2.
|
|
6289
|
+
version: "1.2.2",
|
|
6260
6290
|
name: "SelectiveUI",
|
|
6261
6291
|
bind: SECLASS.bind.bind(SECLASS),
|
|
6262
6292
|
find: SECLASS.find.bind(SECLASS),
|
|
@@ -6287,7 +6317,7 @@
|
|
|
6287
6317
|
init();
|
|
6288
6318
|
}
|
|
6289
6319
|
}
|
|
6290
|
-
console.log(`[${"SelectiveUI"}] v${"1.2.
|
|
6320
|
+
console.log(`[${"SelectiveUI"}] v${"1.2.2"} loaded successfully`);
|
|
6291
6321
|
}
|
|
6292
6322
|
else {
|
|
6293
6323
|
console.warn(`[${globalThis.GLOBAL_SEUI.name}] Already loaded (v${globalThis.GLOBAL_SEUI.version}). ` +
|