selective-ui 1.4.0 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/selective-ui.css +2 -2
- package/dist/selective-ui.css.map +1 -1
- package/dist/selective-ui.esm.js +407 -573
- 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 +409 -575
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +12 -12
- package/src/css/views/option-view.css +2 -2
- package/src/ts/adapter/mixed-adapter.ts +149 -71
- package/src/ts/components/accessorybox.ts +14 -11
- package/src/ts/components/directive.ts +1 -1
- package/src/ts/components/option-handle.ts +12 -9
- package/src/ts/components/placeholder.ts +5 -5
- package/src/ts/components/popup/empty-state.ts +5 -5
- package/src/ts/components/popup/loading-state.ts +5 -5
- package/src/ts/components/popup/popup.ts +138 -76
- package/src/ts/components/searchbox.ts +17 -13
- package/src/ts/components/selectbox.ts +260 -84
- package/src/ts/core/base/adapter.ts +61 -14
- package/src/ts/core/base/fenwick.ts +3 -2
- package/src/ts/core/base/lifecycle.ts +14 -4
- package/src/ts/core/base/model.ts +17 -15
- package/src/ts/core/base/recyclerview.ts +7 -5
- package/src/ts/core/base/view.ts +10 -5
- package/src/ts/core/base/virtual-recyclerview.ts +178 -45
- package/src/ts/core/model-manager.ts +48 -21
- package/src/ts/core/search-controller.ts +174 -56
- package/src/ts/global.ts +5 -8
- package/src/ts/index.ts +2 -2
- package/src/ts/models/group-model.ts +33 -8
- package/src/ts/models/option-model.ts +88 -20
- package/src/ts/services/dataset-observer.ts +6 -3
- package/src/ts/services/ea-observer.ts +1 -1
- package/src/ts/services/effector.ts +22 -12
- package/src/ts/services/refresher.ts +14 -4
- package/src/ts/services/resize-observer.ts +24 -11
- package/src/ts/services/select-observer.ts +2 -2
- package/src/ts/types/components/popup.type.ts +18 -1
- package/src/ts/types/components/searchbox.type.ts +43 -30
- package/src/ts/types/components/state.box.type.ts +1 -1
- package/src/ts/types/core/base/adapter.type.ts +13 -5
- package/src/ts/types/core/base/lifecycle.type.ts +1 -2
- package/src/ts/types/core/base/model.type.ts +3 -3
- package/src/ts/types/core/base/recyclerview.type.ts +7 -5
- package/src/ts/types/core/base/view.type.ts +6 -6
- package/src/ts/types/core/base/virtual-recyclerview.type.ts +45 -46
- package/src/ts/types/core/search-controller.type.ts +18 -2
- package/src/ts/types/css.d.ts +1 -0
- package/src/ts/types/plugins/plugin.type.ts +2 -2
- package/src/ts/types/services/effector.type.ts +25 -25
- package/src/ts/types/services/resize-observer.type.ts +23 -12
- package/src/ts/types/utils/callback-scheduler.type.ts +2 -2
- package/src/ts/types/utils/ievents.type.ts +1 -1
- package/src/ts/types/utils/istorage.type.ts +62 -60
- package/src/ts/types/utils/libs.type.ts +19 -17
- package/src/ts/types/utils/selective.type.ts +6 -3
- package/src/ts/types/views/view.group.type.ts +9 -5
- package/src/ts/types/views/view.option.type.ts +39 -17
- package/src/ts/utils/callback-scheduler.ts +12 -7
- package/src/ts/utils/ievents.ts +12 -5
- package/src/ts/utils/istorage.ts +5 -3
- package/src/ts/utils/libs.ts +122 -43
- package/src/ts/utils/selective.ts +15 -8
- package/src/ts/views/group-view.ts +11 -9
- package/src/ts/views/option-view.ts +37 -18
package/dist/selective-ui.esm.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Selective UI v1.4.
|
|
1
|
+
/*! Selective UI v1.4.2 | MIT License */
|
|
2
2
|
/**
|
|
3
3
|
* @class
|
|
4
4
|
*/
|
|
@@ -147,7 +147,7 @@ class CallbackScheduler {
|
|
|
147
147
|
*
|
|
148
148
|
* @public
|
|
149
149
|
* @param {TimerKey} key - Group identifier for callbacks.
|
|
150
|
-
* @param {(payload
|
|
150
|
+
* @param {(payload?: any[]) => void} callback - Function to execute after debounce timeout.
|
|
151
151
|
* @param {TimerOptions} [options={}] - Scheduling options (`debounce`, `once`).
|
|
152
152
|
* @returns {void}
|
|
153
153
|
*/
|
|
@@ -235,7 +235,8 @@ class CallbackScheduler {
|
|
|
235
235
|
await resp;
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
|
-
catch {
|
|
238
|
+
catch {
|
|
239
|
+
}
|
|
239
240
|
finally {
|
|
240
241
|
if (entry.once) {
|
|
241
242
|
executes[i] = undefined;
|
|
@@ -368,9 +369,11 @@ class Libs {
|
|
|
368
369
|
* @param {boolean} systemNodeCreate - If true, do not clone; use original node.
|
|
369
370
|
* @returns {HTMLElement} - The processed element.
|
|
370
371
|
*/
|
|
371
|
-
static nodeCloner(node = document.documentElement, _nodeOption
|
|
372
|
+
static nodeCloner(node = document.documentElement, _nodeOption, systemNodeCreate = false) {
|
|
372
373
|
const nodeOption = { ...(_nodeOption ?? {}) };
|
|
373
|
-
const element_creation = systemNodeCreate
|
|
374
|
+
const element_creation = systemNodeCreate
|
|
375
|
+
? node
|
|
376
|
+
: node.cloneNode(true);
|
|
374
377
|
const classList = nodeOption.classList;
|
|
375
378
|
if (typeof classList === "string") {
|
|
376
379
|
element_creation.classList.add(classList);
|
|
@@ -442,11 +445,13 @@ class Libs {
|
|
|
442
445
|
* @param {TTags|Object} [recursiveTemp={}] - Accumulator for tag references.
|
|
443
446
|
* @returns {TTags} - Tag map or the final mount result.
|
|
444
447
|
*/
|
|
445
|
-
static mountNode(rawObj, parentE
|
|
448
|
+
static mountNode(rawObj, parentE, isPrepend = false, isRecusive = false, recursiveTemp = {}) {
|
|
446
449
|
let view = null;
|
|
447
450
|
for (const key in rawObj) {
|
|
448
451
|
const singleObj = rawObj[key];
|
|
449
|
-
const tag = singleObj?.tag?.tagName
|
|
452
|
+
const tag = singleObj?.tag?.tagName
|
|
453
|
+
? singleObj.tag
|
|
454
|
+
: this.nodeCreator(singleObj.tag);
|
|
450
455
|
recursiveTemp[key] = tag;
|
|
451
456
|
if (singleObj?.child)
|
|
452
457
|
this.mountNode(singleObj.child, tag, false, false, recursiveTemp);
|
|
@@ -663,7 +668,8 @@ class Libs {
|
|
|
663
668
|
n.removeAttribute(name);
|
|
664
669
|
return;
|
|
665
670
|
}
|
|
666
|
-
if (/^(href|src|xlink:href)$/i.test(name) &&
|
|
671
|
+
if (/^(href|src|xlink:href)$/i.test(name) &&
|
|
672
|
+
/^javascript:/i.test(value)) {
|
|
667
673
|
n.removeAttribute(name);
|
|
668
674
|
}
|
|
669
675
|
}
|
|
@@ -693,7 +699,10 @@ class Libs {
|
|
|
693
699
|
static string2normalize(str) {
|
|
694
700
|
if (str == null)
|
|
695
701
|
return "";
|
|
696
|
-
const s = String(str)
|
|
702
|
+
const s = String(str)
|
|
703
|
+
.toLowerCase()
|
|
704
|
+
.normalize("NFD")
|
|
705
|
+
.replace(/[\u0300-\u036f]/g, "");
|
|
697
706
|
return s.replace(/đ/g, "d").replace(/Đ/g, "d");
|
|
698
707
|
}
|
|
699
708
|
/**
|
|
@@ -735,7 +744,8 @@ class Libs {
|
|
|
735
744
|
*/
|
|
736
745
|
static IsIOS() {
|
|
737
746
|
const ua = navigator.userAgent;
|
|
738
|
-
return /iP(hone|ad|od)/.test(ua) ||
|
|
747
|
+
return (/iP(hone|ad|od)/.test(ua) ||
|
|
748
|
+
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1));
|
|
739
749
|
}
|
|
740
750
|
/**
|
|
741
751
|
* Converts an arbitrary CSS size value into pixel units by measuring a temporary element.
|
|
@@ -757,14 +767,16 @@ class Libs {
|
|
|
757
767
|
if (v.endsWith("rem"))
|
|
758
768
|
return fs * parseFloat(v) + "px";
|
|
759
769
|
// fallback: DOM measure
|
|
760
|
-
const el = this.nodeCreator({
|
|
770
|
+
const el = this.nodeCreator({
|
|
771
|
+
node: "div",
|
|
772
|
+
style: { height: v, opacity: "0" },
|
|
773
|
+
});
|
|
761
774
|
document.body.appendChild(el);
|
|
762
775
|
const px = el.offsetHeight + "px";
|
|
763
776
|
el.remove();
|
|
764
777
|
return px;
|
|
765
778
|
}
|
|
766
779
|
}
|
|
767
|
-
Libs._iStorage = null;
|
|
768
780
|
/**
|
|
769
781
|
* Schedules and batches function executions keyed by name, with debounced timers.
|
|
770
782
|
* Provides setExecute(), clearExecute(), and run() to manage deferred callbacks.
|
|
@@ -960,8 +972,9 @@ class Refresher {
|
|
|
960
972
|
*
|
|
961
973
|
* @param select - Native `<select>` element used as the sizing reference and option source.
|
|
962
974
|
* @param view - View panel element whose inline styles will be updated.
|
|
975
|
+
* @param isWidthOnly - If true, only the width will be updated; height will be left unchanged.
|
|
963
976
|
*/
|
|
964
|
-
static resizeBox(select, view) {
|
|
977
|
+
static resizeBox(select, view, isWidthOnly = false) {
|
|
965
978
|
const bindedMap = Libs.getBinderMap(select);
|
|
966
979
|
if (!bindedMap?.options)
|
|
967
980
|
return;
|
|
@@ -981,7 +994,12 @@ class Refresher {
|
|
|
981
994
|
width = options.width;
|
|
982
995
|
if (cfgHeight > 0)
|
|
983
996
|
height = options.height;
|
|
984
|
-
|
|
997
|
+
if (isWidthOnly) {
|
|
998
|
+
Object.assign(view.style, { width, maxWidth: width, minWidth });
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
Object.assign(view.style, { width, height, maxWidth: width, minWidth, minHeight });
|
|
1002
|
+
}
|
|
985
1003
|
}
|
|
986
1004
|
}
|
|
987
1005
|
|
|
@@ -1291,25 +1309,6 @@ class PlaceHolder extends Lifecycle {
|
|
|
1291
1309
|
*/
|
|
1292
1310
|
constructor(options) {
|
|
1293
1311
|
super();
|
|
1294
|
-
/**
|
|
1295
|
-
* Root DOM element for the placeholder.
|
|
1296
|
-
*
|
|
1297
|
-
* Created during {@link initialize}. Removed from the DOM during {@link destroy}.
|
|
1298
|
-
* `null` before initialization and after destruction.
|
|
1299
|
-
*/
|
|
1300
|
-
this.node = null;
|
|
1301
|
-
/**
|
|
1302
|
-
* Configuration snapshot used to render and optionally persist placeholder content.
|
|
1303
|
-
*
|
|
1304
|
-
* Key fields used by this component:
|
|
1305
|
-
* - `placeholder`: initial/current placeholder text/markup
|
|
1306
|
-
* - `allowHtml`: controls whether HTML is rendered or stripped
|
|
1307
|
-
*
|
|
1308
|
-
* Cleared during {@link destroy}.
|
|
1309
|
-
*
|
|
1310
|
-
* @internal
|
|
1311
|
-
*/
|
|
1312
|
-
this.options = null;
|
|
1313
1312
|
if (options)
|
|
1314
1313
|
this.initialize(options);
|
|
1315
1314
|
}
|
|
@@ -1534,34 +1533,8 @@ class OptionHandle extends Lifecycle {
|
|
|
1534
1533
|
*
|
|
1535
1534
|
* @param options - Feature flags and labels for the two actions.
|
|
1536
1535
|
*/
|
|
1537
|
-
constructor(options
|
|
1536
|
+
constructor(options) {
|
|
1538
1537
|
super();
|
|
1539
|
-
/**
|
|
1540
|
-
* Result returned by {@link Libs.mountNode}.
|
|
1541
|
-
*
|
|
1542
|
-
* Stores the mounted view structure so the component can keep a stable reference
|
|
1543
|
-
* to its created DOM nodes. `null` before {@link initialize}.
|
|
1544
|
-
*
|
|
1545
|
-
* @internal
|
|
1546
|
-
*/
|
|
1547
|
-
this.nodeMounted = null;
|
|
1548
|
-
/**
|
|
1549
|
-
* Root element of this control.
|
|
1550
|
-
*
|
|
1551
|
-
* Created during {@link initialize}. This node is used by {@link show}/{@link hide}
|
|
1552
|
-
* and removed during {@link destroy}.
|
|
1553
|
-
*/
|
|
1554
|
-
this.node = null;
|
|
1555
|
-
/**
|
|
1556
|
-
* Configuration snapshot used for:
|
|
1557
|
-
* - labels (`textSelectAll`, `textDeselectAll`)
|
|
1558
|
-
* - feature flags (`multiple`, `selectall`)
|
|
1559
|
-
*
|
|
1560
|
-
* Treated as read-only after initialization; cleared on {@link destroy}.
|
|
1561
|
-
*
|
|
1562
|
-
* @internal
|
|
1563
|
-
*/
|
|
1564
|
-
this.options = null;
|
|
1565
1538
|
/**
|
|
1566
1539
|
* Callback list invoked when the "Select all" control is activated.
|
|
1567
1540
|
*
|
|
@@ -1649,7 +1622,8 @@ class OptionHandle extends Lifecycle {
|
|
|
1649
1622
|
available() {
|
|
1650
1623
|
if (!this.options)
|
|
1651
1624
|
return false;
|
|
1652
|
-
return Libs.string2Boolean(this.options.multiple) &&
|
|
1625
|
+
return (Libs.string2Boolean(this.options.multiple) &&
|
|
1626
|
+
Libs.string2Boolean(this.options.selectall));
|
|
1653
1627
|
}
|
|
1654
1628
|
/**
|
|
1655
1629
|
* Re-evaluates visibility and advances the lifecycle update step.
|
|
@@ -1706,7 +1680,7 @@ class OptionHandle extends Lifecycle {
|
|
|
1706
1680
|
*
|
|
1707
1681
|
* @param action - Callback invoked on activation; ignored when not a function.
|
|
1708
1682
|
*/
|
|
1709
|
-
onSelectAll(action
|
|
1683
|
+
onSelectAll(action) {
|
|
1710
1684
|
if (typeof action === "function") {
|
|
1711
1685
|
this.actionOnSelectAll.push(action);
|
|
1712
1686
|
}
|
|
@@ -1722,7 +1696,7 @@ class OptionHandle extends Lifecycle {
|
|
|
1722
1696
|
*
|
|
1723
1697
|
* @param action - Callback invoked on activation; ignored when not a function.
|
|
1724
1698
|
*/
|
|
1725
|
-
onDeSelectAll(action
|
|
1699
|
+
onDeSelectAll(action) {
|
|
1726
1700
|
if (typeof action === "function") {
|
|
1727
1701
|
this.actionOnDeSelectAll.push(action);
|
|
1728
1702
|
}
|
|
@@ -1790,26 +1764,10 @@ class EmptyState extends Lifecycle {
|
|
|
1790
1764
|
* If `options` are provided, initialization runs immediately (creates {@link node} and
|
|
1791
1765
|
* transitions to `INITIALIZED`).
|
|
1792
1766
|
*
|
|
1793
|
-
* @param {SelectiveOptions
|
|
1767
|
+
* @param {SelectiveOptions} [options=null] - Configuration containing empty state messages.
|
|
1794
1768
|
*/
|
|
1795
|
-
constructor(options
|
|
1769
|
+
constructor(options) {
|
|
1796
1770
|
super();
|
|
1797
|
-
/**
|
|
1798
|
-
* Root DOM element for the empty state UI.
|
|
1799
|
-
*
|
|
1800
|
-
* - Created during {@link initialize}.
|
|
1801
|
-
* - Intended to be appended by the parent container (component does not auto-attach).
|
|
1802
|
-
* - Removed from DOM during {@link destroy}.
|
|
1803
|
-
*/
|
|
1804
|
-
this.node = null;
|
|
1805
|
-
/**
|
|
1806
|
-
* Configuration source for empty state messages.
|
|
1807
|
-
*
|
|
1808
|
-
* Expected to provide at least:
|
|
1809
|
-
* - `textNoData` (for `"nodata"`)
|
|
1810
|
-
* - `textNotFound` (for `"notfound"`)
|
|
1811
|
-
*/
|
|
1812
|
-
this.options = null;
|
|
1813
1771
|
if (options)
|
|
1814
1772
|
this.initialize(options);
|
|
1815
1773
|
}
|
|
@@ -1935,25 +1893,10 @@ class LoadingState extends Lifecycle {
|
|
|
1935
1893
|
* If `options` are provided, initialization runs immediately (creates {@link node} and
|
|
1936
1894
|
* transitions to `INITIALIZED`).
|
|
1937
1895
|
*
|
|
1938
|
-
* @param {SelectiveOptions
|
|
1896
|
+
* @param {SelectiveOptions} [options=null] - Configuration containing the loading message text.
|
|
1939
1897
|
*/
|
|
1940
|
-
constructor(options
|
|
1898
|
+
constructor(options) {
|
|
1941
1899
|
super();
|
|
1942
|
-
/**
|
|
1943
|
-
* Root DOM element for the loading state UI.
|
|
1944
|
-
*
|
|
1945
|
-
* - Created during {@link initialize}.
|
|
1946
|
-
* - Intended to be appended by the parent container (component does not auto-attach).
|
|
1947
|
-
* - Removed from DOM during {@link destroy}.
|
|
1948
|
-
*/
|
|
1949
|
-
this.node = null;
|
|
1950
|
-
/**
|
|
1951
|
-
* Configuration source for loading message text.
|
|
1952
|
-
*
|
|
1953
|
-
* Expected to provide:
|
|
1954
|
-
* - `textLoading` (displayed while loading is active)
|
|
1955
|
-
*/
|
|
1956
|
-
this.options = null;
|
|
1957
1900
|
if (options)
|
|
1958
1901
|
this.initialize(options);
|
|
1959
1902
|
}
|
|
@@ -2111,27 +2054,6 @@ class ResizeObserverService {
|
|
|
2111
2054
|
* It does **not** indicate that observers are currently attached (see {@link connect}).
|
|
2112
2055
|
*/
|
|
2113
2056
|
this.isInit = false;
|
|
2114
|
-
/**
|
|
2115
|
-
* The currently bound DOM element being observed.
|
|
2116
|
-
*
|
|
2117
|
-
* @remarks
|
|
2118
|
-
* Set by {@link connect} and cleared by {@link disconnect}.
|
|
2119
|
-
*/
|
|
2120
|
-
this.element = null;
|
|
2121
|
-
/**
|
|
2122
|
-
* Underlying `ResizeObserver` instance.
|
|
2123
|
-
*
|
|
2124
|
-
* @remarks
|
|
2125
|
-
* Allocated on {@link connect}. Disconnected and nulled on {@link disconnect}.
|
|
2126
|
-
*/
|
|
2127
|
-
this.resizeObserver = null;
|
|
2128
|
-
/**
|
|
2129
|
-
* Underlying `MutationObserver` instance watching `style` and `class` attribute changes.
|
|
2130
|
-
*
|
|
2131
|
-
* @remarks
|
|
2132
|
-
* Allocated on {@link connect}. Disconnected and nulled on {@link disconnect}.
|
|
2133
|
-
*/
|
|
2134
|
-
this.mutationObserver = null;
|
|
2135
2057
|
this.isInit = true;
|
|
2136
2058
|
this.boundUpdateChanged = this.updateChanged.bind(this);
|
|
2137
2059
|
}
|
|
@@ -2177,7 +2099,8 @@ class ResizeObserverService {
|
|
|
2177
2099
|
return;
|
|
2178
2100
|
}
|
|
2179
2101
|
const rect = el.getBoundingClientRect();
|
|
2180
|
-
const style = typeof window !== "undefined" &&
|
|
2102
|
+
const style = typeof window !== "undefined" &&
|
|
2103
|
+
typeof window.getComputedStyle === "function"
|
|
2181
2104
|
? window.getComputedStyle(el)
|
|
2182
2105
|
: null;
|
|
2183
2106
|
const metrics = {
|
|
@@ -2309,36 +2232,10 @@ class Popup extends Lifecycle {
|
|
|
2309
2232
|
* @param options - Configuration options (panel sizing, flags, texts, etc.).
|
|
2310
2233
|
* @param modelManager - Model manager that supplies the adapter and recycler view.
|
|
2311
2234
|
*/
|
|
2312
|
-
constructor(select
|
|
2235
|
+
constructor(select, options, modelManager) {
|
|
2313
2236
|
super();
|
|
2314
|
-
/** Active configuration for the popup behavior and text labels */
|
|
2315
|
-
this.options = null;
|
|
2316
2237
|
/** Indicates whether the popup DOM has been attached to the document body at least once */
|
|
2317
2238
|
this.isCreated = false;
|
|
2318
|
-
/** Mixed adapter handling items/models and visibility stats */
|
|
2319
|
-
this.optionAdapter = null;
|
|
2320
|
-
/** Root popup container (the floating panel) */
|
|
2321
|
-
this.node = null;
|
|
2322
|
-
/** Effector service used to measure/animate the popup */
|
|
2323
|
-
this.effSvc = null;
|
|
2324
|
-
/** Resize observer to react to parent panel size changes */
|
|
2325
|
-
this.resizeObser = null;
|
|
2326
|
-
/** Binder map for parent elements (anchors to compute placement from) */
|
|
2327
|
-
this.parent = null;
|
|
2328
|
-
/** Header control exposing "Select All / Deselect All" actions */
|
|
2329
|
-
this.optionHandle = null;
|
|
2330
|
-
/** "Empty / Not found" feedback component */
|
|
2331
|
-
this.emptyState = null;
|
|
2332
|
-
/** Loading indicator component */
|
|
2333
|
-
this.loadingState = null;
|
|
2334
|
-
/** Virtualized recycler view for performant lists */
|
|
2335
|
-
this.recyclerView = null;
|
|
2336
|
-
/** Container that holds the list of options */
|
|
2337
|
-
this.optionsContainer = null;
|
|
2338
|
-
/** Scroll handler used by infinite scroll */
|
|
2339
|
-
this.scrollListener = null;
|
|
2340
|
-
/** Handle to defer hiding the loading indicator */
|
|
2341
|
-
this.hideLoadHandle = null;
|
|
2342
2239
|
/** Default virtual scroll configuration (tuned for typical option heights) */
|
|
2343
2240
|
this.virtualScrollConfig = {
|
|
2344
2241
|
/** Estimated item height in pixels (improves initial layout calculation) */
|
|
@@ -2346,7 +2243,7 @@ class Popup extends Lifecycle {
|
|
|
2346
2243
|
/** Number of extra items to render above/below the viewport */
|
|
2347
2244
|
overscan: 8,
|
|
2348
2245
|
/** Whether the list contains items with dynamic (non-uniform) heights */
|
|
2349
|
-
dynamicHeights: true
|
|
2246
|
+
dynamicHeights: true,
|
|
2350
2247
|
};
|
|
2351
2248
|
this.modelManager = modelManager;
|
|
2352
2249
|
if (select && options) {
|
|
@@ -2393,7 +2290,8 @@ class Popup extends Lifecycle {
|
|
|
2393
2290
|
},
|
|
2394
2291
|
}, null);
|
|
2395
2292
|
this.node = nodeMounted.view;
|
|
2396
|
-
this.optionsContainer = nodeMounted.tags
|
|
2293
|
+
this.optionsContainer = nodeMounted.tags
|
|
2294
|
+
.OptionsContainer;
|
|
2397
2295
|
this.parent = Libs.getBinderMap(select);
|
|
2398
2296
|
this.options = options;
|
|
2399
2297
|
this.init();
|
|
@@ -2402,7 +2300,7 @@ class Popup extends Lifecycle {
|
|
|
2402
2300
|
scrollEl: this.node,
|
|
2403
2301
|
estimateItemHeight: this.virtualScrollConfig.estimateItemHeight,
|
|
2404
2302
|
overscan: this.virtualScrollConfig.overscan,
|
|
2405
|
-
dynamicHeights: this.virtualScrollConfig.dynamicHeights
|
|
2303
|
+
dynamicHeights: this.virtualScrollConfig.dynamicHeights,
|
|
2406
2304
|
}
|
|
2407
2305
|
: {};
|
|
2408
2306
|
// Load ModelManager resources into the list container
|
|
@@ -2429,7 +2327,11 @@ class Popup extends Lifecycle {
|
|
|
2429
2327
|
* - Triggers a resize to accommodate layout changes
|
|
2430
2328
|
*/
|
|
2431
2329
|
async showLoading() {
|
|
2432
|
-
if (!this.options ||
|
|
2330
|
+
if (!this.options ||
|
|
2331
|
+
!this.loadingState ||
|
|
2332
|
+
!this.optionHandle ||
|
|
2333
|
+
!this.optionAdapter ||
|
|
2334
|
+
!this.modelManager)
|
|
2433
2335
|
return;
|
|
2434
2336
|
if (this.hideLoadHandle)
|
|
2435
2337
|
clearTimeout(this.hideLoadHandle);
|
|
@@ -2447,7 +2349,10 @@ class Popup extends Lifecycle {
|
|
|
2447
2349
|
* Debounce: Uses `animationtime` as a short delay before hiding the loading indicator.
|
|
2448
2350
|
*/
|
|
2449
2351
|
async hideLoading() {
|
|
2450
|
-
if (!this.options ||
|
|
2352
|
+
if (!this.options ||
|
|
2353
|
+
!this.loadingState ||
|
|
2354
|
+
!this.optionAdapter ||
|
|
2355
|
+
!this.modelManager)
|
|
2451
2356
|
return;
|
|
2452
2357
|
if (this.hideLoadHandle)
|
|
2453
2358
|
clearTimeout(this.hideLoadHandle);
|
|
@@ -2488,7 +2393,10 @@ class Popup extends Lifecycle {
|
|
|
2488
2393
|
* @param stats - Optionally provide precomputed visibility stats.
|
|
2489
2394
|
*/
|
|
2490
2395
|
updateEmptyState(stats) {
|
|
2491
|
-
if (!this.optionAdapter ||
|
|
2396
|
+
if (!this.optionAdapter ||
|
|
2397
|
+
!this.emptyState ||
|
|
2398
|
+
!this.optionHandle ||
|
|
2399
|
+
!this.optionsContainer)
|
|
2492
2400
|
return;
|
|
2493
2401
|
const s = stats ?? this.optionAdapter.getVisibilityStats();
|
|
2494
2402
|
if (s.isEmpty) {
|
|
@@ -2572,8 +2480,12 @@ class Popup extends Lifecycle {
|
|
|
2572
2480
|
* @param callback - Optional callback invoked when the opening animation completes.
|
|
2573
2481
|
* @param isShowEmptyState - If true, applies the empty/not-found state before animation.
|
|
2574
2482
|
*/
|
|
2575
|
-
open(callback
|
|
2576
|
-
if (!this.node ||
|
|
2483
|
+
open(callback, isShowEmptyState) {
|
|
2484
|
+
if (!this.node ||
|
|
2485
|
+
!this.options ||
|
|
2486
|
+
!this.optionHandle ||
|
|
2487
|
+
!this.parent ||
|
|
2488
|
+
!this.effSvc)
|
|
2577
2489
|
return;
|
|
2578
2490
|
// Ensure one-time initialization
|
|
2579
2491
|
this.load();
|
|
@@ -2622,8 +2534,11 @@ class Popup extends Lifecycle {
|
|
|
2622
2534
|
*
|
|
2623
2535
|
* @param callback - Optional callback invoked when the closing animation completes.
|
|
2624
2536
|
*/
|
|
2625
|
-
close(callback
|
|
2626
|
-
if (!this.isCreated ||
|
|
2537
|
+
close(callback) {
|
|
2538
|
+
if (!this.isCreated ||
|
|
2539
|
+
!this.options ||
|
|
2540
|
+
!this.resizeObser ||
|
|
2541
|
+
!this.effSvc)
|
|
2627
2542
|
return;
|
|
2628
2543
|
const rv = this.recyclerView;
|
|
2629
2544
|
rv?.suspend?.();
|
|
@@ -2782,7 +2697,9 @@ class Popup extends Lifecycle {
|
|
|
2782
2697
|
let maxHeight = configMaxHeight;
|
|
2783
2698
|
let realHeight = Math.min(contentHeight, maxHeight);
|
|
2784
2699
|
const heightOri = spaceBelow - safeMargin;
|
|
2785
|
-
if (realHeight >= configMinHeight
|
|
2700
|
+
if (realHeight >= configMinHeight
|
|
2701
|
+
? heightOri >= configMinHeight
|
|
2702
|
+
: heightOri >= realHeight) {
|
|
2786
2703
|
position = "bottom";
|
|
2787
2704
|
maxHeight = Math.min(spaceBelow - safeMargin, configMaxHeight);
|
|
2788
2705
|
}
|
|
@@ -2882,78 +2799,8 @@ class SearchBox extends Lifecycle {
|
|
|
2882
2799
|
*
|
|
2883
2800
|
* @param options - Configuration such as placeholder, accessibility IDs, and flags.
|
|
2884
2801
|
*/
|
|
2885
|
-
constructor(options
|
|
2802
|
+
constructor(options) {
|
|
2886
2803
|
super();
|
|
2887
|
-
/**
|
|
2888
|
-
* The mount result returned by {@link Libs.mountNode}.
|
|
2889
|
-
*
|
|
2890
|
-
* Provides typed access to created DOM tags (e.g., `SearchInput`) and the root view.
|
|
2891
|
-
* `null` before initialization and after destruction.
|
|
2892
|
-
*
|
|
2893
|
-
* @internal
|
|
2894
|
-
*/
|
|
2895
|
-
this.nodeMounted = null;
|
|
2896
|
-
/**
|
|
2897
|
-
* Root container node of this component.
|
|
2898
|
-
*
|
|
2899
|
-
* Created during {@link initialize} and removed during {@link destroy}.
|
|
2900
|
-
* Visibility is controlled by adding/removing the `hide` class.
|
|
2901
|
-
*/
|
|
2902
|
-
this.node = null;
|
|
2903
|
-
/**
|
|
2904
|
-
* The `<input type="search">` element used to capture user queries.
|
|
2905
|
-
*
|
|
2906
|
-
* Cached for imperative operations (focus, placeholder updates, ARIA updates).
|
|
2907
|
-
* `null` before initialization and after destruction.
|
|
2908
|
-
*
|
|
2909
|
-
* @internal
|
|
2910
|
-
*/
|
|
2911
|
-
this.SearchInput = null;
|
|
2912
|
-
/**
|
|
2913
|
-
* External "search changed" hook.
|
|
2914
|
-
*
|
|
2915
|
-
* Invoked when the user edits text (via the `input` event) and the edit is not
|
|
2916
|
-
* part of a handled control-key sequence (e.g., ArrowUp/Down/Tab/Enter/Escape).
|
|
2917
|
-
*
|
|
2918
|
-
* Ownership:
|
|
2919
|
-
* - Implementations typically filter adapter/model state and refresh the list.
|
|
2920
|
-
*/
|
|
2921
|
-
this.onSearch = null;
|
|
2922
|
-
/**
|
|
2923
|
-
* Options snapshot used for behavior toggles and attributes.
|
|
2924
|
-
*
|
|
2925
|
-
* Key fields typically consumed here:
|
|
2926
|
-
* - `placeholder`: initial placeholder string
|
|
2927
|
-
* - `searchable`: toggles readOnly + focus behavior on {@link show}
|
|
2928
|
-
* - `SEID_LIST`: used as `aria-controls` value to bind to listbox container
|
|
2929
|
-
*
|
|
2930
|
-
* Cleared during {@link destroy}.
|
|
2931
|
-
*
|
|
2932
|
-
* @internal
|
|
2933
|
-
*/
|
|
2934
|
-
this.options = null;
|
|
2935
|
-
/**
|
|
2936
|
-
* External navigation hook for list traversal.
|
|
2937
|
-
*
|
|
2938
|
-
* Called with:
|
|
2939
|
-
* - `+1` for forward (ArrowDown / Tab)
|
|
2940
|
-
* - `-1` for backward (ArrowUp)
|
|
2941
|
-
*
|
|
2942
|
-
* Typical consumers update highlight/active option in Adapter/RecyclerView.
|
|
2943
|
-
*/
|
|
2944
|
-
this.onNavigate = null;
|
|
2945
|
-
/**
|
|
2946
|
-
* External "commit" hook (Enter key).
|
|
2947
|
-
*
|
|
2948
|
-
* Typical consumers confirm selection of the highlighted option or submit the current state.
|
|
2949
|
-
*/
|
|
2950
|
-
this.onEnter = null;
|
|
2951
|
-
/**
|
|
2952
|
-
* External "cancel" hook (Escape key).
|
|
2953
|
-
*
|
|
2954
|
-
* Typical consumers close the popup, clear highlight, or reset interaction mode.
|
|
2955
|
-
*/
|
|
2956
|
-
this.onEsc = null;
|
|
2957
2804
|
this.options = options;
|
|
2958
2805
|
if (options)
|
|
2959
2806
|
this.initialize(options);
|
|
@@ -3219,17 +3066,7 @@ class EffectorImpl {
|
|
|
3219
3066
|
*
|
|
3220
3067
|
* @param query - CSS selector or element to control. When `null`, instance starts unbound.
|
|
3221
3068
|
*/
|
|
3222
|
-
constructor(query
|
|
3223
|
-
/**
|
|
3224
|
-
* Timeout used to finalize expand/collapse/swipe animations.
|
|
3225
|
-
* Cleared by {@link cancel}.
|
|
3226
|
-
*/
|
|
3227
|
-
this.timeOut = null;
|
|
3228
|
-
/**
|
|
3229
|
-
* Timeout used to clear transitions after resize in non-animated scenarios.
|
|
3230
|
-
* Cleared by {@link cancel}.
|
|
3231
|
-
*/
|
|
3232
|
-
this.resizeTimeout = null;
|
|
3069
|
+
constructor(query) {
|
|
3233
3070
|
/**
|
|
3234
3071
|
* Internal animation flag set while a timed animation is in-flight.
|
|
3235
3072
|
*
|
|
@@ -3413,7 +3250,9 @@ class EffectorImpl {
|
|
|
3413
3250
|
const { duration = 200, onComplete } = config;
|
|
3414
3251
|
const currentHeight = this.element.offsetHeight;
|
|
3415
3252
|
const currentTop = this.element.offsetTop;
|
|
3416
|
-
const position = this.element.classList.contains("position-top")
|
|
3253
|
+
const position = this.element.classList.contains("position-top")
|
|
3254
|
+
? "top"
|
|
3255
|
+
: "bottom";
|
|
3417
3256
|
const isScrollable = this.element.scrollHeight - this.element.offsetHeight > 0;
|
|
3418
3257
|
const finalTop = position === "top" ? currentTop + currentHeight : currentTop;
|
|
3419
3258
|
requestAnimationFrame(() => {
|
|
@@ -3551,7 +3390,9 @@ class EffectorImpl {
|
|
|
3551
3390
|
return this;
|
|
3552
3391
|
this.cancel();
|
|
3553
3392
|
const { duration = 200, width, left, top, maxHeight, realHeight, position = "bottom", animate = true, onComplete, } = config;
|
|
3554
|
-
const currentPosition = this.element.classList.contains("position-top")
|
|
3393
|
+
const currentPosition = this.element.classList.contains("position-top")
|
|
3394
|
+
? "top"
|
|
3395
|
+
: "bottom";
|
|
3555
3396
|
const isPositionChanged = currentPosition !== position;
|
|
3556
3397
|
const isScrollable = this.element.scrollHeight > maxHeight;
|
|
3557
3398
|
this.element.classList.toggle("position-top", position === "top");
|
|
@@ -3667,25 +3508,11 @@ class Model extends Lifecycle {
|
|
|
3667
3508
|
* - Calls {@link Lifecycle.init} immediately (`NEW → INITIALIZED`).
|
|
3668
3509
|
*
|
|
3669
3510
|
* @param {TOptions} options - Configuration options for the model.
|
|
3670
|
-
* @param {TTarget
|
|
3671
|
-
* @param {TView
|
|
3511
|
+
* @param {TTarget} [targetElement=null] - Optional DOM element to bind.
|
|
3512
|
+
* @param {TView} [view=null] - Optional view responsible for rendering this model.
|
|
3672
3513
|
*/
|
|
3673
|
-
constructor(options, targetElement
|
|
3514
|
+
constructor(options, targetElement, view) {
|
|
3674
3515
|
super();
|
|
3675
|
-
/**
|
|
3676
|
-
* The currently bound target DOM element.
|
|
3677
|
-
*
|
|
3678
|
-
* This element typically represents the source-of-truth node in the host DOM (e.g., a native `<option>`).
|
|
3679
|
-
* May be replaced via {@link updateTarget} during reconciliation.
|
|
3680
|
-
*/
|
|
3681
|
-
this.targetElement = null;
|
|
3682
|
-
/**
|
|
3683
|
-
* View instance responsible for rendering this model.
|
|
3684
|
-
*
|
|
3685
|
-
* Ownership: this model will destroy the view on {@link destroy}.
|
|
3686
|
-
* The view may be attached/assigned by external orchestrators (Adapter/RecyclerView) after construction.
|
|
3687
|
-
*/
|
|
3688
|
-
this.view = null;
|
|
3689
3516
|
/**
|
|
3690
3517
|
* Position index used by list infrastructure for ordering/tracking.
|
|
3691
3518
|
* Semantics are library-specific (e.g., top-level index or adapter position).
|
|
@@ -3717,7 +3544,7 @@ class Model extends Lifecycle {
|
|
|
3717
3544
|
* - Assigns {@link targetElement}.
|
|
3718
3545
|
* - Calls {@link Lifecycle.update} (guarded by lifecycle state).
|
|
3719
3546
|
*
|
|
3720
|
-
* @param {TTarget
|
|
3547
|
+
* @param {TTarget} targetElement - The new DOM element to associate with this model.
|
|
3721
3548
|
* @returns {void}
|
|
3722
3549
|
*/
|
|
3723
3550
|
updateTarget(targetElement) {
|
|
@@ -3917,7 +3744,7 @@ class GroupModel extends Model {
|
|
|
3917
3744
|
if (this.is(LifecycleState.DESTROYED)) {
|
|
3918
3745
|
return;
|
|
3919
3746
|
}
|
|
3920
|
-
this.items.forEach(item => {
|
|
3747
|
+
this.items.forEach((item) => {
|
|
3921
3748
|
item.destroy();
|
|
3922
3749
|
});
|
|
3923
3750
|
this.items = [];
|
|
@@ -4041,10 +3868,10 @@ class OptionModel extends Model {
|
|
|
4041
3868
|
* Creates an option model.
|
|
4042
3869
|
*
|
|
4043
3870
|
* @param {SelectiveOptions} options - Shared configuration for models/views.
|
|
4044
|
-
* @param {HTMLOptionElement
|
|
4045
|
-
* @param {OptionView
|
|
3871
|
+
* @param {HTMLOptionElement} [targetElement=null] - Backing `<option>` element.
|
|
3872
|
+
* @param {OptionView} [view=null] - Optional view used to render this model.
|
|
4046
3873
|
*/
|
|
4047
|
-
constructor(options, targetElement
|
|
3874
|
+
constructor(options, targetElement, view) {
|
|
4048
3875
|
super(options, targetElement, view);
|
|
4049
3876
|
/**
|
|
4050
3877
|
* External selection subscribers (emitted by the {@link selected} setter).
|
|
@@ -4068,11 +3895,6 @@ class OptionModel extends Model {
|
|
|
4068
3895
|
this._visible = true;
|
|
4069
3896
|
/** Highlight flag used for keyboard navigation / hover. */
|
|
4070
3897
|
this._highlighted = false;
|
|
4071
|
-
/**
|
|
4072
|
-
* Parent group model (if this option belongs to a group).
|
|
4073
|
-
* Assigned by grouping logic (e.g., GroupModel/MixedAdapter).
|
|
4074
|
-
*/
|
|
4075
|
-
this.group = null;
|
|
4076
3898
|
}
|
|
4077
3899
|
/**
|
|
4078
3900
|
* Initializes the model and precomputes the search key.
|
|
@@ -4201,6 +4023,32 @@ class OptionModel extends Model {
|
|
|
4201
4023
|
}
|
|
4202
4024
|
iEvents.callEvent([this, value], ...this.privOnInternalSelected);
|
|
4203
4025
|
}
|
|
4026
|
+
/**
|
|
4027
|
+
* Resolved display mask for this option.
|
|
4028
|
+
*
|
|
4029
|
+
* The mask is the primary render label used by the UI layer and supports
|
|
4030
|
+
* optional inline tag translation / rich HTML rendering.
|
|
4031
|
+
*
|
|
4032
|
+
* Source priority:
|
|
4033
|
+
* 1. `data-mask` (`dataset.mask`)
|
|
4034
|
+
* 2. Native `<option>` text content (`targetElement.text`)
|
|
4035
|
+
*
|
|
4036
|
+
* Processing pipeline:
|
|
4037
|
+
* - Raw content is first passed through {@link Libs.tagTranslate}.
|
|
4038
|
+
* - When `options.allowHtml === true`, translated HTML is preserved.
|
|
4039
|
+
* - Otherwise, all markup is stripped via {@link Libs.stripHtml}.
|
|
4040
|
+
*
|
|
4041
|
+
* Unlike {@link text}, this getter prioritizes the custom dataset mask,
|
|
4042
|
+
* making it suitable for display overrides without mutating the native
|
|
4043
|
+
* `<option>` label.
|
|
4044
|
+
*
|
|
4045
|
+
* @returns {string} Render-ready option label.
|
|
4046
|
+
*/
|
|
4047
|
+
get mask() {
|
|
4048
|
+
const raw = this.dataset?.mask ?? this.targetElement?.text ?? "";
|
|
4049
|
+
const translated = Libs.tagTranslate(raw);
|
|
4050
|
+
return this.options.allowHtml ? translated : Libs.stripHtml(translated);
|
|
4051
|
+
}
|
|
4204
4052
|
/**
|
|
4205
4053
|
* Display label for rendering (with tag translation and HTML policy).
|
|
4206
4054
|
*
|
|
@@ -4214,7 +4062,7 @@ class OptionModel extends Model {
|
|
|
4214
4062
|
* @returns {string}
|
|
4215
4063
|
*/
|
|
4216
4064
|
get text() {
|
|
4217
|
-
const raw = this.
|
|
4065
|
+
const raw = this.targetElement?.text ?? this.dataset?.mask ?? "";
|
|
4218
4066
|
const translated = Libs.tagTranslate(raw);
|
|
4219
4067
|
return this.options.allowHtml ? translated : Libs.stripHtml(translated);
|
|
4220
4068
|
}
|
|
@@ -4227,7 +4075,9 @@ class OptionModel extends Model {
|
|
|
4227
4075
|
* @returns {string}
|
|
4228
4076
|
*/
|
|
4229
4077
|
get textContent() {
|
|
4230
|
-
return this.options.allowHtml
|
|
4078
|
+
return this.options.allowHtml
|
|
4079
|
+
? Libs.stripHtml(this.text).trim()
|
|
4080
|
+
: this.text.trim();
|
|
4231
4081
|
}
|
|
4232
4082
|
/**
|
|
4233
4083
|
* Dataset object of the backing `<option>` element.
|
|
@@ -4408,8 +4258,6 @@ class ModelManager extends Lifecycle {
|
|
|
4408
4258
|
constructor(options) {
|
|
4409
4259
|
super();
|
|
4410
4260
|
this.privModelList = [];
|
|
4411
|
-
this.privAdapterHandle = null;
|
|
4412
|
-
this.privRecyclerViewHandle = null;
|
|
4413
4261
|
this.options = null;
|
|
4414
4262
|
this.oldPosition = 0;
|
|
4415
4263
|
this.options = options;
|
|
@@ -4461,7 +4309,9 @@ class ModelManager extends Lifecycle {
|
|
|
4461
4309
|
else if (data.tagName === "OPTION") {
|
|
4462
4310
|
const optionModel = new OptionModel(this.options, data);
|
|
4463
4311
|
const parentGroup = data["__parentGroup"];
|
|
4464
|
-
if (parentGroup &&
|
|
4312
|
+
if (parentGroup &&
|
|
4313
|
+
currentGroup &&
|
|
4314
|
+
parentGroup === currentGroup.targetElement) {
|
|
4465
4315
|
currentGroup.addItem(optionModel);
|
|
4466
4316
|
optionModel.group = currentGroup;
|
|
4467
4317
|
}
|
|
@@ -4886,44 +4736,10 @@ class AccessoryBox extends Lifecycle {
|
|
|
4886
4736
|
/**
|
|
4887
4737
|
* Creates an AccessoryBox and optionally initializes it with configuration.
|
|
4888
4738
|
*
|
|
4889
|
-
* @param {SelectiveOptions
|
|
4739
|
+
* @param {SelectiveOptions} [options=null] - Configuration controlling placement/visibility and texts.
|
|
4890
4740
|
*/
|
|
4891
|
-
constructor(options
|
|
4741
|
+
constructor(options) {
|
|
4892
4742
|
super();
|
|
4893
|
-
/**
|
|
4894
|
-
* Mounted structure returned by the node mounting helper.
|
|
4895
|
-
* Contains the root element (`view`) and any tag handles (if present).
|
|
4896
|
-
*/
|
|
4897
|
-
this.nodeMounted = null;
|
|
4898
|
-
/**
|
|
4899
|
-
* Root DOM element of the accessory box (hidden by default).
|
|
4900
|
-
* Created during {@link init} and removed during {@link destroy}.
|
|
4901
|
-
*/
|
|
4902
|
-
this.node = null;
|
|
4903
|
-
/**
|
|
4904
|
-
* Component configuration (texts, behavior, placement).
|
|
4905
|
-
* This component reads:
|
|
4906
|
-
* - `accessoryStyle` ("top" or default bottom)
|
|
4907
|
-
* - `accessoryVisible` (enable/disable)
|
|
4908
|
-
* - `multiple` (multi-select mode)
|
|
4909
|
-
* - `textAccessoryDeselect` (a11y label prefix)
|
|
4910
|
-
*/
|
|
4911
|
-
this.options = null;
|
|
4912
|
-
/**
|
|
4913
|
-
* The Select UI mask element used as the positioning reference.
|
|
4914
|
-
* Provided by {@link setRoot}.
|
|
4915
|
-
*/
|
|
4916
|
-
this.selectUIMask = null;
|
|
4917
|
-
/**
|
|
4918
|
-
* Parent container that hosts both the Select UI mask and the accessory box.
|
|
4919
|
-
* Computed from `selectUIMask.parentElement`.
|
|
4920
|
-
*/
|
|
4921
|
-
this.parentMask = null;
|
|
4922
|
-
/**
|
|
4923
|
-
* ModelManager used to run selection pipelines and coordinate state updates.
|
|
4924
|
-
* This component does not own selection state; it delegates to the model layer.
|
|
4925
|
-
*/
|
|
4926
|
-
this.modelManager = null;
|
|
4927
4743
|
/**
|
|
4928
4744
|
* Current selected option models rendered as chips.
|
|
4929
4745
|
* This is a cached snapshot used for show/hide decisions and re-rendering.
|
|
@@ -5231,15 +5047,6 @@ class SearchController extends Lifecycle {
|
|
|
5231
5047
|
*/
|
|
5232
5048
|
constructor(selectElement, modelManager, selectBox) {
|
|
5233
5049
|
super();
|
|
5234
|
-
/**
|
|
5235
|
-
* AJAX configuration; when `null`, {@link search} falls back to local filtering.
|
|
5236
|
-
* @see {@link setAjax}
|
|
5237
|
-
*/
|
|
5238
|
-
this.ajaxConfig = null;
|
|
5239
|
-
/** Abort handle used to cancel an in-flight AJAX request when a newer request starts. */
|
|
5240
|
-
this.abortController = null;
|
|
5241
|
-
/** Optional popup handle used for showing/hiding loading UI during remote operations. */
|
|
5242
|
-
this.popup = null;
|
|
5243
5050
|
/**
|
|
5244
5051
|
* SelectBox handle used by custom data builder functions that require Selective context.
|
|
5245
5052
|
* NOTE: This is a reference; the controller does not own/destroy the SelectBox.
|
|
@@ -5303,7 +5110,11 @@ class SearchController extends Lifecycle {
|
|
|
5303
5110
|
*/
|
|
5304
5111
|
async loadByValues(values) {
|
|
5305
5112
|
if (!this.ajaxConfig) {
|
|
5306
|
-
return {
|
|
5113
|
+
return {
|
|
5114
|
+
success: false,
|
|
5115
|
+
items: [],
|
|
5116
|
+
message: "Ajax not configured",
|
|
5117
|
+
};
|
|
5307
5118
|
}
|
|
5308
5119
|
const valuesArray = Array.isArray(values) ? values : [values];
|
|
5309
5120
|
if (valuesArray.length === 0)
|
|
@@ -5320,7 +5131,7 @@ class SearchController extends Lifecycle {
|
|
|
5320
5131
|
load_by_values: "1",
|
|
5321
5132
|
...(typeof cfg.data === "function"
|
|
5322
5133
|
? cfg.data.bind(this.selectBox.Selective.find(this.selectBox.container.targetElement))("", 0)
|
|
5323
|
-
: cfg.data ?? {}),
|
|
5134
|
+
: (cfg.data ?? {})),
|
|
5324
5135
|
};
|
|
5325
5136
|
}
|
|
5326
5137
|
let response;
|
|
@@ -5330,7 +5141,9 @@ class SearchController extends Lifecycle {
|
|
|
5330
5141
|
response = await fetch(cfg.url, {
|
|
5331
5142
|
method: "POST",
|
|
5332
5143
|
body: formData,
|
|
5333
|
-
headers: {
|
|
5144
|
+
headers: {
|
|
5145
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
5146
|
+
},
|
|
5334
5147
|
});
|
|
5335
5148
|
}
|
|
5336
5149
|
else {
|
|
@@ -5367,7 +5180,7 @@ class SearchController extends Lifecycle {
|
|
|
5367
5180
|
* Configures AJAX settings used for remote searching and pagination.
|
|
5368
5181
|
* Setting `null` disables AJAX mode and causes {@link search} to use local filtering.
|
|
5369
5182
|
*
|
|
5370
|
-
* @param {AjaxConfig
|
|
5183
|
+
* @param {AjaxConfig} config - AJAX configuration (endpoint, method, data builders, keepSelected, ...).
|
|
5371
5184
|
* @returns {void}
|
|
5372
5185
|
*/
|
|
5373
5186
|
setAjax(config) {
|
|
@@ -5557,7 +5370,12 @@ class SearchController extends Lifecycle {
|
|
|
5557
5370
|
payload.selectedValue = selectedValues;
|
|
5558
5371
|
}
|
|
5559
5372
|
else {
|
|
5560
|
-
payload = {
|
|
5373
|
+
payload = {
|
|
5374
|
+
search: keyword,
|
|
5375
|
+
page,
|
|
5376
|
+
selectedValue: selectedValues,
|
|
5377
|
+
...(cfg.data ?? {}),
|
|
5378
|
+
};
|
|
5561
5379
|
}
|
|
5562
5380
|
try {
|
|
5563
5381
|
let response;
|
|
@@ -5567,13 +5385,17 @@ class SearchController extends Lifecycle {
|
|
|
5567
5385
|
response = await fetch(cfg.url, {
|
|
5568
5386
|
method: "POST",
|
|
5569
5387
|
body: formData,
|
|
5570
|
-
headers: {
|
|
5388
|
+
headers: {
|
|
5389
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
5390
|
+
},
|
|
5571
5391
|
signal: this.abortController.signal,
|
|
5572
5392
|
});
|
|
5573
5393
|
}
|
|
5574
5394
|
else {
|
|
5575
5395
|
const params = new URLSearchParams(payload).toString();
|
|
5576
|
-
response = await fetch(`${cfg.url}?${params}`, {
|
|
5396
|
+
response = await fetch(`${cfg.url}?${params}`, {
|
|
5397
|
+
signal: this.abortController.signal,
|
|
5398
|
+
});
|
|
5577
5399
|
}
|
|
5578
5400
|
const data = await response.json();
|
|
5579
5401
|
const result = this.parseResponse(data);
|
|
@@ -5647,7 +5469,7 @@ class SearchController extends Lifecycle {
|
|
|
5647
5469
|
hasPagination = true;
|
|
5648
5470
|
page = parseInt(data.page ?? 0, 10);
|
|
5649
5471
|
totalPages = parseInt(data.totalPages ?? data.total_page ?? 1, 10);
|
|
5650
|
-
hasMore = data.hasMore ??
|
|
5472
|
+
hasMore = data.hasMore ?? page < totalPages - 1;
|
|
5651
5473
|
}
|
|
5652
5474
|
}
|
|
5653
5475
|
else if (Array.isArray(data)) {
|
|
@@ -5658,23 +5480,39 @@ class SearchController extends Lifecycle {
|
|
|
5658
5480
|
if (data.pagination) {
|
|
5659
5481
|
hasPagination = true;
|
|
5660
5482
|
page = parseInt(data.pagination.page ?? 0, 10);
|
|
5661
|
-
totalPages = parseInt(data.pagination.totalPages ??
|
|
5662
|
-
|
|
5483
|
+
totalPages = parseInt(data.pagination.totalPages ??
|
|
5484
|
+
data.pagination.total_page ??
|
|
5485
|
+
1, 10);
|
|
5486
|
+
hasMore = data.pagination.hasMore ?? page < totalPages - 1;
|
|
5663
5487
|
}
|
|
5664
5488
|
}
|
|
5665
5489
|
const normalized = items.map((item) => {
|
|
5666
|
-
if (item instanceof HTMLOptionElement ||
|
|
5490
|
+
if (item instanceof HTMLOptionElement ||
|
|
5491
|
+
item instanceof HTMLOptGroupElement)
|
|
5667
5492
|
return item;
|
|
5668
|
-
if (item.type === "optgroup" ||
|
|
5493
|
+
if (item.type === "optgroup" ||
|
|
5494
|
+
item.isGroup ||
|
|
5495
|
+
item.group ||
|
|
5496
|
+
item.label) {
|
|
5669
5497
|
const label = item.label ?? item.name ?? item.title ?? "";
|
|
5670
5498
|
const dataObj = item.data ?? {};
|
|
5671
5499
|
const opts = (item.options ?? item.items ?? []).map((opt) => ({
|
|
5672
5500
|
value: opt.value ?? opt.id ?? opt.key ?? "",
|
|
5673
|
-
text: opt.text ??
|
|
5501
|
+
text: opt.text ??
|
|
5502
|
+
opt.label ??
|
|
5503
|
+
opt.name ??
|
|
5504
|
+
opt.title ??
|
|
5505
|
+
"",
|
|
5674
5506
|
selected: opt.selected ?? false,
|
|
5675
|
-
data: opt.data ??
|
|
5507
|
+
data: opt.data ??
|
|
5508
|
+
(opt.imgsrc ? { imgsrc: opt.imgsrc } : {}),
|
|
5676
5509
|
}));
|
|
5677
|
-
return {
|
|
5510
|
+
return {
|
|
5511
|
+
type: "optgroup",
|
|
5512
|
+
label,
|
|
5513
|
+
data: dataObj,
|
|
5514
|
+
options: opts,
|
|
5515
|
+
};
|
|
5678
5516
|
}
|
|
5679
5517
|
const dataObj = item.data ?? {};
|
|
5680
5518
|
if (item?.imgsrc)
|
|
@@ -5716,9 +5554,12 @@ class SearchController extends Lifecycle {
|
|
|
5716
5554
|
select.innerHTML = "";
|
|
5717
5555
|
items.forEach((item) => {
|
|
5718
5556
|
// Skip empty item (defensive guard)
|
|
5719
|
-
if ((item["type"] === "option" || !item["type"]) &&
|
|
5557
|
+
if ((item["type"] === "option" || !item["type"]) &&
|
|
5558
|
+
item["value"] === "" &&
|
|
5559
|
+
item["text"] === "")
|
|
5720
5560
|
return;
|
|
5721
|
-
if (item instanceof HTMLOptionElement ||
|
|
5561
|
+
if (item instanceof HTMLOptionElement ||
|
|
5562
|
+
item instanceof HTMLOptGroupElement) {
|
|
5722
5563
|
select.appendChild(item);
|
|
5723
5564
|
return;
|
|
5724
5565
|
}
|
|
@@ -5740,7 +5581,8 @@ class SearchController extends Lifecycle {
|
|
|
5740
5581
|
option.dataset[key] = String(opt.data[key]);
|
|
5741
5582
|
});
|
|
5742
5583
|
}
|
|
5743
|
-
if (opt.selected ||
|
|
5584
|
+
if (opt.selected ||
|
|
5585
|
+
(keepSelected && oldSelected.includes(option.value))) {
|
|
5744
5586
|
option.selected = true;
|
|
5745
5587
|
}
|
|
5746
5588
|
optgroup.appendChild(option);
|
|
@@ -5757,7 +5599,8 @@ class SearchController extends Lifecycle {
|
|
|
5757
5599
|
option.dataset[key] = String(item.data[key]);
|
|
5758
5600
|
});
|
|
5759
5601
|
}
|
|
5760
|
-
if (item.selected ||
|
|
5602
|
+
if (item.selected ||
|
|
5603
|
+
(keepSelected && oldSelected.includes(option.value))) {
|
|
5761
5604
|
option.selected = true;
|
|
5762
5605
|
}
|
|
5763
5606
|
select.appendChild(option);
|
|
@@ -5833,16 +5676,6 @@ class SelectObserver {
|
|
|
5833
5676
|
* @param {HTMLSelectElement} select - The `<select>` element to observe for mutations.
|
|
5834
5677
|
*/
|
|
5835
5678
|
constructor(select) {
|
|
5836
|
-
/**
|
|
5837
|
-
* Debounce timer handle for batching rapid mutations.
|
|
5838
|
-
*
|
|
5839
|
-
* - Cleared and reset on each mutation event.
|
|
5840
|
-
* - Invokes {@link handleChange} after {@link _DEBOUNCE_DELAY} milliseconds of inactivity.
|
|
5841
|
-
* - Nulled during {@link disconnect}.
|
|
5842
|
-
*
|
|
5843
|
-
* @private
|
|
5844
|
-
*/
|
|
5845
|
-
this.debounceTimer = null;
|
|
5846
5679
|
/**
|
|
5847
5680
|
* Debounce delay in milliseconds.
|
|
5848
5681
|
*
|
|
@@ -5983,16 +5816,12 @@ class DatasetObserver {
|
|
|
5983
5816
|
* @param element - The element whose `data-*` attributes will be observed.
|
|
5984
5817
|
*/
|
|
5985
5818
|
constructor(element) {
|
|
5986
|
-
/**
|
|
5987
|
-
* Debounce timer handle for coalescing rapid attribute mutations.
|
|
5988
|
-
* Cleared/replaced whenever a new relevant mutation arrives within the debounce window.
|
|
5989
|
-
*/
|
|
5990
|
-
this.debounceTimer = null;
|
|
5991
5819
|
this.element = element;
|
|
5992
5820
|
this.observer = new MutationObserver((mutations) => {
|
|
5993
5821
|
let datasetChanged = false;
|
|
5994
5822
|
for (const mutation of mutations) {
|
|
5995
|
-
if (mutation.type === "attributes" &&
|
|
5823
|
+
if (mutation.type === "attributes" &&
|
|
5824
|
+
mutation.attributeName?.startsWith("data-")) {
|
|
5996
5825
|
datasetChanged = true;
|
|
5997
5826
|
break;
|
|
5998
5827
|
}
|
|
@@ -6141,6 +5970,16 @@ class Adapter extends Lifecycle {
|
|
|
6141
5970
|
* This flag is intentionally generic and is coordinated by higher-level components.
|
|
6142
5971
|
*/
|
|
6143
5972
|
this.isSkipEvent = false;
|
|
5973
|
+
/**
|
|
5974
|
+
* Tracks all scheduler keys registered by this adapter instance via
|
|
5975
|
+
* {@link onPropChanging} and {@link onPropChanged}.
|
|
5976
|
+
*
|
|
5977
|
+
* Used during {@link destroy} to clean up all associated pipelines
|
|
5978
|
+
* from the global {@link Libs.callbackScheduler}.
|
|
5979
|
+
*
|
|
5980
|
+
* Keys are deduplicated automatically by Set semantics.
|
|
5981
|
+
*/
|
|
5982
|
+
this.callbackSchedulerList = new Set();
|
|
6144
5983
|
this.items = items;
|
|
6145
5984
|
this.init();
|
|
6146
5985
|
}
|
|
@@ -6182,7 +6021,9 @@ class Adapter extends Lifecycle {
|
|
|
6182
6021
|
* @see {@link changingProp}
|
|
6183
6022
|
*/
|
|
6184
6023
|
onPropChanging(propName, callback) {
|
|
6185
|
-
|
|
6024
|
+
const key = `${propName}ing_${this.adapterKey}`;
|
|
6025
|
+
Libs.callbackScheduler.on(key, callback, { debounce: 0 });
|
|
6026
|
+
this.callbackSchedulerList.add(key);
|
|
6186
6027
|
}
|
|
6187
6028
|
/**
|
|
6188
6029
|
* Registers a **post-change** callback for a property pipeline.
|
|
@@ -6198,7 +6039,11 @@ class Adapter extends Lifecycle {
|
|
|
6198
6039
|
* @see {@link changeProp}
|
|
6199
6040
|
*/
|
|
6200
6041
|
onPropChanged(propName, callback) {
|
|
6201
|
-
|
|
6042
|
+
const key = `${propName}_${this.adapterKey}`;
|
|
6043
|
+
Libs.callbackScheduler.on(key, callback, {
|
|
6044
|
+
debounce: 0,
|
|
6045
|
+
});
|
|
6046
|
+
this.callbackSchedulerList.add(key);
|
|
6202
6047
|
}
|
|
6203
6048
|
/**
|
|
6204
6049
|
* Triggers the **post-change** pipeline for a given property.
|
|
@@ -6233,7 +6078,7 @@ class Adapter extends Lifecycle {
|
|
|
6233
6078
|
*
|
|
6234
6079
|
* @param {HTMLElement} parent - Container element that will host the viewer.
|
|
6235
6080
|
* @param {TItem} item - The model for which the viewer is created.
|
|
6236
|
-
* @returns {TViewer
|
|
6081
|
+
* @returns {TViewer} The created viewer instance; `null` by default.
|
|
6237
6082
|
*/
|
|
6238
6083
|
viewHolder(parent, item) {
|
|
6239
6084
|
return null;
|
|
@@ -6339,11 +6184,16 @@ class Adapter extends Lifecycle {
|
|
|
6339
6184
|
if (this.is(LifecycleState.DESTROYED)) {
|
|
6340
6185
|
return;
|
|
6341
6186
|
}
|
|
6187
|
+
this.callbackSchedulerList.forEach((key) => {
|
|
6188
|
+
Libs.callbackScheduler.off(key);
|
|
6189
|
+
});
|
|
6190
|
+
this.callbackSchedulerList.clear();
|
|
6342
6191
|
this.recyclerView = null;
|
|
6343
|
-
this.items.forEach(item => {
|
|
6192
|
+
this.items.forEach((item) => {
|
|
6344
6193
|
item?.destroy?.();
|
|
6345
6194
|
});
|
|
6346
6195
|
this.items = [];
|
|
6196
|
+
super.destroy();
|
|
6347
6197
|
}
|
|
6348
6198
|
}
|
|
6349
6199
|
|
|
@@ -6395,20 +6245,6 @@ class View extends Lifecycle {
|
|
|
6395
6245
|
*/
|
|
6396
6246
|
constructor(parent) {
|
|
6397
6247
|
super();
|
|
6398
|
-
/**
|
|
6399
|
-
* Host container element into which this view's root element is rendered/attached.
|
|
6400
|
-
*
|
|
6401
|
-
* This reference is captured at construction time and cleared on {@link destroy}.
|
|
6402
|
-
*/
|
|
6403
|
-
this.parent = null;
|
|
6404
|
-
/**
|
|
6405
|
-
* Mounted view result containing:
|
|
6406
|
-
* - `view`: the root element of this view
|
|
6407
|
-
* - `tags`: a strongly-typed map of child elements for fast access
|
|
6408
|
-
*
|
|
6409
|
-
* This is expected to be assigned by subclasses (or a mount helper) before {@link getView} is called.
|
|
6410
|
-
*/
|
|
6411
|
-
this.view = null;
|
|
6412
6248
|
this.parent = parent;
|
|
6413
6249
|
this.init();
|
|
6414
6250
|
}
|
|
@@ -6509,29 +6345,6 @@ class GroupView extends View {
|
|
|
6509
6345
|
*/
|
|
6510
6346
|
constructor(parent, options) {
|
|
6511
6347
|
super(parent);
|
|
6512
|
-
/**
|
|
6513
|
-
* Strongly-typed reference to the mounted group view structure.
|
|
6514
|
-
*
|
|
6515
|
-
* Structure:
|
|
6516
|
-
* - **view**: Root container element.
|
|
6517
|
-
* - **tags**: Named references to header and items container.
|
|
6518
|
-
*
|
|
6519
|
-
* Lifecycle:
|
|
6520
|
-
* - `null` until {@link mount} completes.
|
|
6521
|
-
* - Cleared during {@link destroy}.
|
|
6522
|
-
*
|
|
6523
|
-
* @public
|
|
6524
|
-
*/
|
|
6525
|
-
this.view = null;
|
|
6526
|
-
/**
|
|
6527
|
-
* Parsed configuration (bound from the `<select>` element via binder map).
|
|
6528
|
-
*
|
|
6529
|
-
* Provides feature flags (multiple/disabled/readonly/visible/virtualScroll/ajax/autoclose…),
|
|
6530
|
-
* a11y ids (e.g. `SEID_LIST`, `SEID_HOLDER`) and user callbacks under `options.on`.
|
|
6531
|
-
*
|
|
6532
|
-
* @internal
|
|
6533
|
-
*/
|
|
6534
|
-
this.options = null;
|
|
6535
6348
|
this.options = options;
|
|
6536
6349
|
}
|
|
6537
6350
|
/**
|
|
@@ -6625,10 +6438,10 @@ class GroupView extends View {
|
|
|
6625
6438
|
* - Safe to call multiple times with same value (idempotent).
|
|
6626
6439
|
*
|
|
6627
6440
|
* @public
|
|
6628
|
-
* @param {string
|
|
6441
|
+
* @param {string} [label=null] - New label to display; `null` preserves current label.
|
|
6629
6442
|
* @returns {void}
|
|
6630
6443
|
*/
|
|
6631
|
-
updateLabel(label
|
|
6444
|
+
updateLabel(label) {
|
|
6632
6445
|
if (!this.view)
|
|
6633
6446
|
return;
|
|
6634
6447
|
const headerEl = this.view.tags.GroupHeader;
|
|
@@ -6675,7 +6488,7 @@ class GroupView extends View {
|
|
|
6675
6488
|
if (!this.view)
|
|
6676
6489
|
return;
|
|
6677
6490
|
const items = this.view.tags.GroupItems;
|
|
6678
|
-
const visibleItems = Array.from(items.children).filter(child => !child.classList.contains("hide"));
|
|
6491
|
+
const visibleItems = Array.from(items.children).filter((child) => !child.classList.contains("hide"));
|
|
6679
6492
|
this.view.view.classList.toggle("hide", visibleItems.length === 0);
|
|
6680
6493
|
}
|
|
6681
6494
|
/**
|
|
@@ -6789,57 +6602,6 @@ class OptionView extends View {
|
|
|
6789
6602
|
*/
|
|
6790
6603
|
constructor(parent, options) {
|
|
6791
6604
|
super(parent);
|
|
6792
|
-
/**
|
|
6793
|
-
* Strongly-typed reference to the mounted option view structure.
|
|
6794
|
-
*
|
|
6795
|
-
* Structure:
|
|
6796
|
-
* - **view**: Root container element.
|
|
6797
|
-
* - **tags**: Named references to input, image (conditional), label, label content.
|
|
6798
|
-
*
|
|
6799
|
-
* Lifecycle:
|
|
6800
|
-
* - `null` until {@link mount} completes.
|
|
6801
|
-
* - Cleared during {@link destroy}.
|
|
6802
|
-
*
|
|
6803
|
-
* @public
|
|
6804
|
-
*/
|
|
6805
|
-
this.view = null;
|
|
6806
|
-
/**
|
|
6807
|
-
* Parsed configuration (bound from the `<select>` element via binder map).
|
|
6808
|
-
*
|
|
6809
|
-
* Provides feature flags (multiple/disabled/readonly/visible/virtualScroll/ajax/autoclose…),
|
|
6810
|
-
* a11y ids (e.g. `SEID_LIST`, `SEID_HOLDER`) and user callbacks under `options.on`.
|
|
6811
|
-
*
|
|
6812
|
-
* @internal
|
|
6813
|
-
*/
|
|
6814
|
-
this.options = null;
|
|
6815
|
-
/**
|
|
6816
|
-
* Internal configuration object (Proxy target).
|
|
6817
|
-
*
|
|
6818
|
-
* Lifecycle:
|
|
6819
|
-
* - Initialized during {@link initialize} with default values.
|
|
6820
|
-
* - Mutated via {@link configProxy} Proxy trap.
|
|
6821
|
-
*
|
|
6822
|
-
* Notes:
|
|
6823
|
-
* - **Should not be mutated directly**; use {@link configProxy} or typed setters.
|
|
6824
|
-
* - Contains default values for layout, image, and alignment.
|
|
6825
|
-
*
|
|
6826
|
-
* @private
|
|
6827
|
-
*/
|
|
6828
|
-
this.config = null;
|
|
6829
|
-
/**
|
|
6830
|
-
* Reactive Proxy wrapper around {@link config}.
|
|
6831
|
-
*
|
|
6832
|
-
* Behavior:
|
|
6833
|
-
* - Intercepts property assignments via `set` trap.
|
|
6834
|
-
* - Triggers {@link applyPartialChange} for diffed values when {@link isRendered} is `true`.
|
|
6835
|
-
* - Prevents redundant DOM updates when value hasn't changed.
|
|
6836
|
-
*
|
|
6837
|
-
* Usage:
|
|
6838
|
-
* - Accessed via {@link optionConfig} getter or typed setters ({@link isMultiple}, {@link hasImage}).
|
|
6839
|
-
*
|
|
6840
|
-
* @private
|
|
6841
|
-
*/
|
|
6842
|
-
this.configProxy = null;
|
|
6843
6605
|
/**
|
|
6844
6606
|
* Flag indicating whether the initial render has completed.
|
|
6845
6607
|
*
|
|
@@ -7004,24 +6766,30 @@ class OptionView extends View {
|
|
|
7004
6766
|
* - Each changed property triggers {@link applyPartialChange} individually.
|
|
7005
6767
|
*
|
|
7006
6768
|
* @public
|
|
7007
|
-
* @param {OptionConfigPatch
|
|
6769
|
+
* @param {OptionConfigPatch} config - Partial configuration patch; `null` is no-op.
|
|
7008
6770
|
* @returns {void}
|
|
7009
6771
|
*/
|
|
7010
6772
|
set optionConfig(config) {
|
|
7011
6773
|
if (!config || !this.configProxy || !this.config)
|
|
7012
6774
|
return;
|
|
7013
6775
|
const changes = {};
|
|
7014
|
-
if (config.imageWidth !== undefined &&
|
|
6776
|
+
if (config.imageWidth !== undefined &&
|
|
6777
|
+
config.imageWidth !== this.config.imageWidth)
|
|
7015
6778
|
changes.imageWidth = config.imageWidth;
|
|
7016
|
-
if (config.imageHeight !== undefined &&
|
|
6779
|
+
if (config.imageHeight !== undefined &&
|
|
6780
|
+
config.imageHeight !== this.config.imageHeight)
|
|
7017
6781
|
changes.imageHeight = config.imageHeight;
|
|
7018
|
-
if (config.imageBorderRadius !== undefined &&
|
|
6782
|
+
if (config.imageBorderRadius !== undefined &&
|
|
6783
|
+
config.imageBorderRadius !== this.config.imageBorderRadius)
|
|
7019
6784
|
changes.imageBorderRadius = config.imageBorderRadius;
|
|
7020
|
-
if (config.imagePosition !== undefined &&
|
|
6785
|
+
if (config.imagePosition !== undefined &&
|
|
6786
|
+
config.imagePosition !== this.config.imagePosition)
|
|
7021
6787
|
changes.imagePosition = config.imagePosition;
|
|
7022
|
-
if (config.labelValign !== undefined &&
|
|
6788
|
+
if (config.labelValign !== undefined &&
|
|
6789
|
+
config.labelValign !== this.config.labelValign)
|
|
7023
6790
|
changes.labelValign = config.labelValign;
|
|
7024
|
-
if (config.labelHalign !== undefined &&
|
|
6791
|
+
if (config.labelHalign !== undefined &&
|
|
6792
|
+
config.labelHalign !== this.config.labelHalign)
|
|
7025
6793
|
changes.labelHalign = config.labelHalign;
|
|
7026
6794
|
if (Object.keys(changes).length > 0) {
|
|
7027
6795
|
Object.assign(this.configProxy, changes);
|
|
@@ -7195,9 +6963,11 @@ class OptionView extends View {
|
|
|
7195
6963
|
case "imageBorderRadius": {
|
|
7196
6964
|
const img = v.tags?.OptionImage;
|
|
7197
6965
|
if (img) {
|
|
7198
|
-
const styleProp = prop === "imageWidth"
|
|
7199
|
-
|
|
7200
|
-
|
|
6966
|
+
const styleProp = prop === "imageWidth"
|
|
6967
|
+
? "width"
|
|
6968
|
+
: prop === "imageHeight"
|
|
6969
|
+
? "height"
|
|
6970
|
+
: "borderRadius";
|
|
7201
6971
|
img.style[styleProp] = String(newValue);
|
|
7202
6972
|
}
|
|
7203
6973
|
break;
|
|
@@ -7318,15 +7088,6 @@ class MixedAdapter extends Adapter {
|
|
|
7318
7088
|
super(items);
|
|
7319
7089
|
/** Whether the adapter operates in multi-selection mode. */
|
|
7320
7090
|
this.isMultiple = false;
|
|
7321
|
-
/**
|
|
7322
|
-
* Parsed configuration (bound from the `<select>` element via binder map).
|
|
7323
|
-
*
|
|
7324
|
-
* Provides feature flags (multiple/disabled/readonly/visible/virtualScroll/ajax/autoclose…),
|
|
7325
|
-
* a11y ids (e.g. `SEID_LIST`, `SEID_HOLDER`) and user callbacks under `options.on`.
|
|
7326
|
-
*
|
|
7327
|
-
* @internal
|
|
7328
|
-
*/
|
|
7329
|
-
this.options = null;
|
|
7330
7091
|
/**
|
|
7331
7092
|
* Subscribers for aggregated visibility statistics.
|
|
7332
7093
|
* Fired via a debounced scheduler to avoid repeated recomputation during batch updates.
|
|
@@ -7337,11 +7098,6 @@ class MixedAdapter extends Adapter {
|
|
|
7337
7098
|
* `-1` indicates "no highlight".
|
|
7338
7099
|
*/
|
|
7339
7100
|
this.currentHighlightIndex = -1;
|
|
7340
|
-
/**
|
|
7341
|
-
* Cached pointer to the selected option in single-select mode.
|
|
7342
|
-
* Used to efficiently clear previous selection when selecting a new option.
|
|
7343
|
-
*/
|
|
7344
|
-
this.selectedItemSingle = null;
|
|
7345
7101
|
/** Top-level group models (if any). */
|
|
7346
7102
|
this.groups = [];
|
|
7347
7103
|
/**
|
|
@@ -7430,7 +7186,7 @@ class MixedAdapter extends Adapter {
|
|
|
7430
7186
|
* - Performs one-time listener binding guarded by `item.isInit`.
|
|
7431
7187
|
*
|
|
7432
7188
|
* @param {MixedItem} item - {@link GroupModel} or {@link OptionModel}.
|
|
7433
|
-
* @param {GroupView | OptionView
|
|
7189
|
+
* @param {GroupView | OptionView} viewer - The view instance that will render the model.
|
|
7434
7190
|
* @param {number} position - Position in the top-level mixed list.
|
|
7435
7191
|
* @returns {void}
|
|
7436
7192
|
* @override
|
|
@@ -7657,7 +7413,7 @@ class MixedAdapter extends Adapter {
|
|
|
7657
7413
|
return;
|
|
7658
7414
|
}
|
|
7659
7415
|
Libs.callbackScheduler.clear(`sche_vis_${this.adapterKey}`);
|
|
7660
|
-
this.groups.forEach(group => {
|
|
7416
|
+
this.groups.forEach((group) => {
|
|
7661
7417
|
group.destroy();
|
|
7662
7418
|
});
|
|
7663
7419
|
this.visibilityChangedCallbacks = [];
|
|
@@ -7737,7 +7493,7 @@ class MixedAdapter extends Adapter {
|
|
|
7737
7493
|
* @returns {void}
|
|
7738
7494
|
*/
|
|
7739
7495
|
resetHighlight() {
|
|
7740
|
-
this.setHighlight(0);
|
|
7496
|
+
this.setHighlight(0, false);
|
|
7741
7497
|
}
|
|
7742
7498
|
/**
|
|
7743
7499
|
* Moves highlight among **visible** options and optionally scrolls the new target into view.
|
|
@@ -7780,7 +7536,8 @@ class MixedAdapter extends Adapter {
|
|
|
7780
7536
|
* @returns {void}
|
|
7781
7537
|
*/
|
|
7782
7538
|
selectHighlighted() {
|
|
7783
|
-
if (this.currentHighlightIndex > -1 &&
|
|
7539
|
+
if (this.currentHighlightIndex > -1 &&
|
|
7540
|
+
this.flatOptions[this.currentHighlightIndex]) {
|
|
7784
7541
|
const item = this.flatOptions[this.currentHighlightIndex];
|
|
7785
7542
|
if (item.visible) {
|
|
7786
7543
|
const viewEl = item.view?.getView?.();
|
|
@@ -7817,7 +7574,8 @@ class MixedAdapter extends Adapter {
|
|
|
7817
7574
|
else {
|
|
7818
7575
|
index = 0;
|
|
7819
7576
|
}
|
|
7820
|
-
if (this.currentHighlightIndex > -1 &&
|
|
7577
|
+
if (this.currentHighlightIndex > -1 &&
|
|
7578
|
+
this.flatOptions[this.currentHighlightIndex]) {
|
|
7821
7579
|
this.flatOptions[this.currentHighlightIndex].highlighted = false;
|
|
7822
7580
|
}
|
|
7823
7581
|
for (let i = index; i < this.flatOptions.length; i++) {
|
|
@@ -7828,12 +7586,13 @@ class MixedAdapter extends Adapter {
|
|
|
7828
7586
|
this.currentHighlightIndex = i;
|
|
7829
7587
|
if (isScrollToView) {
|
|
7830
7588
|
const el = item.view?.getView?.();
|
|
7831
|
-
if (el) {
|
|
7832
|
-
el.scrollIntoView({ block:
|
|
7589
|
+
if (el?.isConnected) {
|
|
7590
|
+
el.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
7833
7591
|
}
|
|
7834
7592
|
else {
|
|
7835
|
-
|
|
7836
|
-
|
|
7593
|
+
this.recyclerView?.ensureRendered?.(i, {
|
|
7594
|
+
scrollIntoView: true,
|
|
7595
|
+
});
|
|
7837
7596
|
}
|
|
7838
7597
|
}
|
|
7839
7598
|
this.onHighlightChange(i, item.view?.getView?.()?.id);
|
|
@@ -8062,9 +7821,9 @@ class VirtualRecyclerView extends RecyclerView {
|
|
|
8062
7821
|
*
|
|
8063
7822
|
* Note: The virtualization scaffold is built when an adapter is set via {@link setAdapter}.
|
|
8064
7823
|
*
|
|
8065
|
-
* @param {HTMLDivElement
|
|
7824
|
+
* @param {HTMLDivElement} [viewElement=null] - Optional root container for the recycler view.
|
|
8066
7825
|
*/
|
|
8067
|
-
constructor(viewElement
|
|
7826
|
+
constructor(viewElement) {
|
|
8068
7827
|
super(viewElement);
|
|
8069
7828
|
/**
|
|
8070
7829
|
* Virtualization settings (materialized to `Required<VirtualOptions>`).
|
|
@@ -8100,15 +7859,18 @@ class VirtualRecyclerView extends RecyclerView {
|
|
|
8100
7859
|
this.start = 0;
|
|
8101
7860
|
/** Current window end (inclusive). -1 means not initialized. */
|
|
8102
7861
|
this.end = -1;
|
|
8103
|
-
/** Pending animation frame ids for window and measurement. */
|
|
8104
|
-
this.rafId = null;
|
|
8105
|
-
this.measureRaf = null;
|
|
8106
7862
|
/** Re-entrancy/suspension flags used to prevent feedback loops. */
|
|
8107
7863
|
this.updating = false;
|
|
8108
7864
|
this.suppressResize = false;
|
|
8109
7865
|
this.lastRenderCount = 0;
|
|
8110
7866
|
this.suspended = false;
|
|
8111
7867
|
this.resumeResizeAfter = false;
|
|
7868
|
+
/**
|
|
7869
|
+
* When set, scrollToIndex() will be called after the next measureVisibleAndUpdate()
|
|
7870
|
+
* completes and Fenwick has been updated with real heights.
|
|
7871
|
+
* Set by ensureRendered() and cleared after the corrective scroll fires.
|
|
7872
|
+
*/
|
|
7873
|
+
this.pendingScrollToIndex = null;
|
|
8112
7874
|
/** Small cache for sticky header height (≈16ms TTL) to limit layout reads. */
|
|
8113
7875
|
this.stickyCacheTick = 0;
|
|
8114
7876
|
this.stickyCacheVal = 0;
|
|
@@ -8157,20 +7919,29 @@ class VirtualRecyclerView extends RecyclerView {
|
|
|
8157
7919
|
return;
|
|
8158
7920
|
this.viewElement.replaceChildren();
|
|
8159
7921
|
const nodeMounted = Libs.mountNode({
|
|
8160
|
-
PadTop: {
|
|
8161
|
-
|
|
8162
|
-
|
|
7922
|
+
PadTop: {
|
|
7923
|
+
tag: { node: "div", classList: "seui-virtual-pad-top" },
|
|
7924
|
+
},
|
|
7925
|
+
ItemsHost: {
|
|
7926
|
+
tag: { node: "div", classList: "seui-virtual-items" },
|
|
7927
|
+
},
|
|
7928
|
+
PadBottom: {
|
|
7929
|
+
tag: { node: "div", classList: "seui-virtual-pad-bottom" },
|
|
7930
|
+
},
|
|
8163
7931
|
}, this.viewElement);
|
|
8164
7932
|
this.PadTop = nodeMounted.PadTop;
|
|
8165
7933
|
this.ItemsHost = nodeMounted.ItemsHost;
|
|
8166
7934
|
this.PadBottom = nodeMounted.PadBottom;
|
|
8167
|
-
this.scrollEl =
|
|
8168
|
-
|
|
8169
|
-
|
|
7935
|
+
this.scrollEl =
|
|
7936
|
+
this.opts.scrollEl ??
|
|
7937
|
+
this.viewElement.closest(".seui-popup") ??
|
|
7938
|
+
this.viewElement.parentElement;
|
|
8170
7939
|
if (!this.scrollEl)
|
|
8171
7940
|
throw new Error("VirtualRecyclerView: scrollEl not found");
|
|
8172
7941
|
this.boundOnScroll = this.onScroll.bind(this);
|
|
8173
|
-
this.scrollEl.addEventListener("scroll", this.boundOnScroll, {
|
|
7942
|
+
this.scrollEl.addEventListener("scroll", this.boundOnScroll, {
|
|
7943
|
+
passive: true,
|
|
7944
|
+
});
|
|
8174
7945
|
this.refresh(false);
|
|
8175
7946
|
this.attachResizeObserverOnce();
|
|
8176
7947
|
adapter?.onVisibilityChanged?.(() => this.refreshItem());
|
|
@@ -8209,7 +7980,9 @@ class VirtualRecyclerView extends RecyclerView {
|
|
|
8209
7980
|
resume() {
|
|
8210
7981
|
this.suspended = false;
|
|
8211
7982
|
if (this.scrollEl && this.boundOnScroll) {
|
|
8212
|
-
this.scrollEl.addEventListener("scroll", this.boundOnScroll, {
|
|
7983
|
+
this.scrollEl.addEventListener("scroll", this.boundOnScroll, {
|
|
7984
|
+
passive: true,
|
|
7985
|
+
});
|
|
8213
7986
|
}
|
|
8214
7987
|
if (this.resumeResizeAfter) {
|
|
8215
7988
|
this.attachResizeObserverOnce();
|
|
@@ -8264,9 +8037,19 @@ class VirtualRecyclerView extends RecyclerView {
|
|
|
8264
8037
|
* @returns {void}
|
|
8265
8038
|
*/
|
|
8266
8039
|
ensureRendered(index, opt) {
|
|
8267
|
-
|
|
8268
|
-
|
|
8269
|
-
this.
|
|
8040
|
+
if (!opt?.scrollIntoView) {
|
|
8041
|
+
// No scroll requested — mount only (legacy path, used by probes).
|
|
8042
|
+
this.mountRange(index, index);
|
|
8043
|
+
return;
|
|
8044
|
+
}
|
|
8045
|
+
// Pass 1: instant — brings window to vicinity, triggers measure.
|
|
8046
|
+
// Must be instant so Pass 2 smooth scroll isn't interrupted mid-animation.
|
|
8047
|
+
this.scrollToIndex(index, "instant");
|
|
8048
|
+
// Pass 2: measureVisibleAndUpdate() will consume this and fire a corrective
|
|
8049
|
+
// smooth scroll after Fenwick has been updated with real heights.
|
|
8050
|
+
// rv.resume() is guaranteed to run before this callback (popup.open onComplete
|
|
8051
|
+
// calls rv.resume() first), so the window is already rendered when we arrive here.
|
|
8052
|
+
this.pendingScrollToIndex = index;
|
|
8270
8053
|
}
|
|
8271
8054
|
/**
|
|
8272
8055
|
* Scrolls the scroll container to align the item at `index` into view.
|
|
@@ -8280,15 +8063,25 @@ class VirtualRecyclerView extends RecyclerView {
|
|
|
8280
8063
|
* @param {number} index - Item index to bring into view.
|
|
8281
8064
|
* @returns {void}
|
|
8282
8065
|
*/
|
|
8283
|
-
scrollToIndex(index) {
|
|
8066
|
+
scrollToIndex(index, behavior = "smooth") {
|
|
8284
8067
|
const count = this.adapter?.itemCount?.() ?? 0;
|
|
8285
8068
|
if (count <= 0)
|
|
8286
8069
|
return;
|
|
8287
8070
|
const topInContainer = this.offsetTopOf(index);
|
|
8288
8071
|
const containerTop = this.containerTopInScroll();
|
|
8289
|
-
const
|
|
8072
|
+
const stickyH = this.stickyTopHeight();
|
|
8073
|
+
const viewportH = Math.max(0, this.scrollEl.clientHeight - stickyH);
|
|
8074
|
+
// item height from cache, or current estimate for unmeasured items
|
|
8075
|
+
const est = this.getEstimate();
|
|
8076
|
+
const itemH = this.heightCache[index] ?? est;
|
|
8077
|
+
// Align item center to viewport center (below any sticky header).
|
|
8078
|
+
// viewportH already excludes stickyH, so no further offset needed.
|
|
8079
|
+
// Equivalent to scrollIntoView({ block: "center" }).
|
|
8080
|
+
const centeredTarget = containerTop + topInContainer
|
|
8081
|
+
- (viewportH - itemH) / 3;
|
|
8290
8082
|
const maxScroll = Math.max(0, this.scrollEl.scrollHeight - this.scrollEl.clientHeight);
|
|
8291
|
-
|
|
8083
|
+
const clamped = Math.min(Math.max(0, centeredTarget), maxScroll);
|
|
8084
|
+
this.scrollEl.scrollTo({ top: clamped, behavior });
|
|
8292
8085
|
}
|
|
8293
8086
|
/**
|
|
8294
8087
|
* Disposes runtime resources without destroying the instance.
|
|
@@ -8307,7 +8100,7 @@ class VirtualRecyclerView extends RecyclerView {
|
|
|
8307
8100
|
this.scrollEl.removeEventListener("scroll", this.boundOnScroll);
|
|
8308
8101
|
}
|
|
8309
8102
|
this.resizeObs?.disconnect();
|
|
8310
|
-
this.created.forEach(el => el.remove());
|
|
8103
|
+
this.created.forEach((el) => el.remove());
|
|
8311
8104
|
this.created.clear();
|
|
8312
8105
|
}
|
|
8313
8106
|
/**
|
|
@@ -8355,9 +8148,22 @@ class VirtualRecyclerView extends RecyclerView {
|
|
|
8355
8148
|
if (count <= 0)
|
|
8356
8149
|
return;
|
|
8357
8150
|
this.suspend();
|
|
8358
|
-
this.
|
|
8151
|
+
this.pendingScrollToIndex = null;
|
|
8152
|
+
// When visibility changes (search filter applied or cleared), heightCache may
|
|
8153
|
+
// contain heights measured while only a subset of items was visible. Re-using
|
|
8154
|
+
// these partial measurements in rebuildFenwick() causes incorrect prefix sums
|
|
8155
|
+
// (e.g. items measured while scrolled into a filtered window have real heights,
|
|
8156
|
+
// while surrounding items still use estimates — creating an uneven Fenwick).
|
|
8157
|
+
//
|
|
8158
|
+
// Safe fix: clear heightCache entirely on visibility change. The adaptive
|
|
8159
|
+
// estimator will re-seed from probeInitialHeight() on the next render, and
|
|
8160
|
+
// items will be re-measured as they scroll into view.
|
|
8161
|
+
this.heightCache = [];
|
|
8162
|
+
this.measuredSum = 0;
|
|
8163
|
+
this.measuredCount = 0;
|
|
8164
|
+
this.firstMeasured = false;
|
|
8165
|
+
this.resetDOM();
|
|
8359
8166
|
this.cleanupInvisibleItems();
|
|
8360
|
-
this.recomputeMeasuredStats(count);
|
|
8361
8167
|
this.rebuildFenwick(count);
|
|
8362
8168
|
this.start = 0;
|
|
8363
8169
|
this.end = -1;
|
|
@@ -8375,7 +8181,30 @@ class VirtualRecyclerView extends RecyclerView {
|
|
|
8375
8181
|
}
|
|
8376
8182
|
}
|
|
8377
8183
|
/**
|
|
8378
|
-
* Resets
|
|
8184
|
+
* Resets DOM nodes, Fenwick sums, padding, and estimator stats — but preserves {@link heightCache}.
|
|
8185
|
+
*
|
|
8186
|
+
* Use this inside {@link refreshItem} so that {@link recomputeMeasuredStats} can still
|
|
8187
|
+
* read previously measured heights before the Fenwick tree is rebuilt.
|
|
8188
|
+
*
|
|
8189
|
+
* DOM side effects:
|
|
8190
|
+
* - Removes all currently mounted item elements tracked in {@link created}.
|
|
8191
|
+
* - Resets pad heights to `0px`.
|
|
8192
|
+
*
|
|
8193
|
+
* @returns {void}
|
|
8194
|
+
*/
|
|
8195
|
+
resetDOM() {
|
|
8196
|
+
this.created.forEach((el) => el.remove());
|
|
8197
|
+
this.created.clear();
|
|
8198
|
+
this.fenwick.reset(0);
|
|
8199
|
+
this.PadTop.style.height = "0px";
|
|
8200
|
+
this.PadBottom.style.height = "0px";
|
|
8201
|
+
this.firstMeasured = false;
|
|
8202
|
+
}
|
|
8203
|
+
/**
|
|
8204
|
+
* Full reset: clears DOM nodes, Fenwick sums, padding, estimator stats, AND {@link heightCache}.
|
|
8205
|
+
*
|
|
8206
|
+
* Use this for complete teardown (e.g., adapter swap, destroy sequence) where all
|
|
8207
|
+
* cached measurements should be discarded.
|
|
8379
8208
|
*
|
|
8380
8209
|
* DOM side effects:
|
|
8381
8210
|
* - Removes all currently mounted item elements tracked in {@link created}.
|
|
@@ -8384,7 +8213,7 @@ class VirtualRecyclerView extends RecyclerView {
|
|
|
8384
8213
|
* @returns {void}
|
|
8385
8214
|
*/
|
|
8386
8215
|
resetState() {
|
|
8387
|
-
this.created.forEach(el => el.remove());
|
|
8216
|
+
this.created.forEach((el) => el.remove());
|
|
8388
8217
|
this.created.clear();
|
|
8389
8218
|
this.heightCache = [];
|
|
8390
8219
|
this.fenwick.reset(0);
|
|
@@ -8658,8 +8487,12 @@ class VirtualRecyclerView extends RecyclerView {
|
|
|
8658
8487
|
el.setAttribute(VirtualRecyclerView.ATTR_INDEX, String(index));
|
|
8659
8488
|
const prev = el.previousElementSibling;
|
|
8660
8489
|
const next = el.nextElementSibling;
|
|
8661
|
-
const needsReorder = (prev &&
|
|
8662
|
-
|
|
8490
|
+
const needsReorder = (prev &&
|
|
8491
|
+
Number(prev.getAttribute(VirtualRecyclerView.ATTR_INDEX)) >
|
|
8492
|
+
index) ||
|
|
8493
|
+
(next &&
|
|
8494
|
+
Number(next.getAttribute(VirtualRecyclerView.ATTR_INDEX)) <
|
|
8495
|
+
index);
|
|
8663
8496
|
if (needsReorder) {
|
|
8664
8497
|
el.remove();
|
|
8665
8498
|
this.insertIntoHostByIndex(index, el);
|
|
@@ -8681,7 +8514,10 @@ class VirtualRecyclerView extends RecyclerView {
|
|
|
8681
8514
|
if (this.resizeObs)
|
|
8682
8515
|
return;
|
|
8683
8516
|
this.resizeObs = new ResizeObserver(() => {
|
|
8684
|
-
if (this.suppressResize ||
|
|
8517
|
+
if (this.suppressResize ||
|
|
8518
|
+
this.suspended ||
|
|
8519
|
+
!this.adapter ||
|
|
8520
|
+
this.measureRaf != null)
|
|
8685
8521
|
return;
|
|
8686
8522
|
this.measureRaf = requestAnimationFrame(() => {
|
|
8687
8523
|
this.measureRaf = null;
|
|
@@ -8722,6 +8558,15 @@ class VirtualRecyclerView extends RecyclerView {
|
|
|
8722
8558
|
this.rebuildFenwick(count);
|
|
8723
8559
|
this.scheduleUpdateWindow();
|
|
8724
8560
|
}
|
|
8561
|
+
// Corrective scroll: if ensureRendered() registered a target index, fire
|
|
8562
|
+
// scrollToIndex() now that real heights are in Fenwick. Clear the target
|
|
8563
|
+
// first to prevent infinite re-triggering (scrollToIndex may cause another
|
|
8564
|
+
// measure cycle, but heights won't change so changed === false next time).
|
|
8565
|
+
if (this.pendingScrollToIndex !== null) {
|
|
8566
|
+
const target = this.pendingScrollToIndex;
|
|
8567
|
+
this.pendingScrollToIndex = null;
|
|
8568
|
+
this.scrollToIndex(target, "smooth");
|
|
8569
|
+
}
|
|
8725
8570
|
}
|
|
8726
8571
|
/**
|
|
8727
8572
|
* Scroll event handler. Schedules a window update on the next frame.
|
|
@@ -9010,31 +8855,6 @@ class SelectBox extends Lifecycle {
|
|
|
9010
8855
|
* @internal
|
|
9011
8856
|
*/
|
|
9012
8857
|
this.oldValue = null;
|
|
9013
|
-
/**
|
|
9014
|
-
* Root wrapper DOM node for the enhanced UI.
|
|
9015
|
-
*
|
|
9016
|
-
* Created during {@link init} via {@link Libs.mountNode}, inserted into the DOM during {@link mount},
|
|
9017
|
-
* and removed during {@link destroy}.
|
|
9018
|
-
*/
|
|
9019
|
-
this.node = null;
|
|
9020
|
-
/**
|
|
9021
|
-
* Parsed configuration (bound from the `<select>` element via binder map).
|
|
9022
|
-
*
|
|
9023
|
-
* Provides feature flags (multiple/disabled/readonly/visible/virtualScroll/ajax/autoclose…),
|
|
9024
|
-
* a11y ids (e.g. `SEID_LIST`, `SEID_HOLDER`) and user callbacks under `options.on`.
|
|
9025
|
-
*
|
|
9026
|
-
* @internal
|
|
9027
|
-
*/
|
|
9028
|
-
this.options = null;
|
|
9029
|
-
/**
|
|
9030
|
-
* Manager that owns model resources and bridges the Adapter ↔ RecyclerView pipeline.
|
|
9031
|
-
*
|
|
9032
|
-
* The configured adapter is {@link MixedAdapter}. The recyclerview implementation is chosen
|
|
9033
|
-
* based on `options.virtualScroll` (standard {@link RecyclerView} vs {@link VirtualRecyclerView}).
|
|
9034
|
-
*
|
|
9035
|
-
* @internal
|
|
9036
|
-
*/
|
|
9037
|
-
this.optionModelManager = null;
|
|
9038
8858
|
/**
|
|
9039
8859
|
* Whether the popup/list UI is currently open.
|
|
9040
8860
|
*
|
|
@@ -9066,20 +8886,10 @@ class SelectBox extends Lifecycle {
|
|
|
9066
8886
|
* @internal
|
|
9067
8887
|
*/
|
|
9068
8888
|
this.hasDeInitialized = false;
|
|
9069
|
-
/**
|
|
9070
|
-
* Selective context (global helper / registry).
|
|
9071
|
-
*
|
|
9072
|
-
* Used to locate the instance wrapper via `Selective.find(...)` and to close other open instances.
|
|
9073
|
-
*/
|
|
9074
|
-
this.Selective = null;
|
|
9075
8889
|
/**
|
|
9076
8890
|
* Registered plugins for this SelectBox instance.
|
|
9077
8891
|
*/
|
|
9078
8892
|
this.plugins = [];
|
|
9079
|
-
/**
|
|
9080
|
-
* Cached plugin context for this SelectBox instance.
|
|
9081
|
-
*/
|
|
9082
|
-
this.pluginContext = null;
|
|
9083
8893
|
if (select && Selective)
|
|
9084
8894
|
this.initialize(select, Selective);
|
|
9085
8895
|
}
|
|
@@ -9204,7 +9014,9 @@ class SelectBox extends Lifecycle {
|
|
|
9204
9014
|
classList: "seui-view",
|
|
9205
9015
|
tabIndex: 0,
|
|
9206
9016
|
onkeydown: (e) => {
|
|
9207
|
-
if (e.key === "Enter" ||
|
|
9017
|
+
if (e.key === "Enter" ||
|
|
9018
|
+
e.key === " " ||
|
|
9019
|
+
e.key === "ArrowDown") {
|
|
9208
9020
|
e.preventDefault();
|
|
9209
9021
|
this.getAction()?.open();
|
|
9210
9022
|
}
|
|
@@ -9310,6 +9122,7 @@ class SelectBox extends Lifecycle {
|
|
|
9310
9122
|
e.preventDefault();
|
|
9311
9123
|
});
|
|
9312
9124
|
Refresher.resizeBox(select, container.tags.ViewPanel);
|
|
9125
|
+
Refresher.resizeBox(select, this.node, true);
|
|
9313
9126
|
select.classList.add("init");
|
|
9314
9127
|
// initial mask
|
|
9315
9128
|
const action = this.getAction();
|
|
@@ -9596,7 +9409,11 @@ class SelectBox extends Lifecycle {
|
|
|
9596
9409
|
get value() {
|
|
9597
9410
|
const item_list = this.valueArray;
|
|
9598
9411
|
const valLength = item_list.length;
|
|
9599
|
-
return valLength > 1
|
|
9412
|
+
return valLength > 1
|
|
9413
|
+
? item_list
|
|
9414
|
+
: valLength === 0
|
|
9415
|
+
? ""
|
|
9416
|
+
: item_list[0];
|
|
9600
9417
|
},
|
|
9601
9418
|
get valueArray() {
|
|
9602
9419
|
const item_list = [];
|
|
@@ -9621,7 +9438,7 @@ class SelectBox extends Lifecycle {
|
|
|
9621
9438
|
get mask() {
|
|
9622
9439
|
const item_list = [];
|
|
9623
9440
|
superThis.getModelOption(true).forEach((m) => {
|
|
9624
|
-
item_list.push(m.
|
|
9441
|
+
item_list.push(m.mask);
|
|
9625
9442
|
});
|
|
9626
9443
|
return item_list;
|
|
9627
9444
|
},
|
|
@@ -9631,7 +9448,11 @@ class SelectBox extends Lifecycle {
|
|
|
9631
9448
|
item_list.push(m.text);
|
|
9632
9449
|
});
|
|
9633
9450
|
const valLength = item_list.length;
|
|
9634
|
-
return valLength > 1
|
|
9451
|
+
return valLength > 1
|
|
9452
|
+
? item_list
|
|
9453
|
+
: valLength === 0
|
|
9454
|
+
? ""
|
|
9455
|
+
: item_list[0];
|
|
9635
9456
|
},
|
|
9636
9457
|
get isOpen() {
|
|
9637
9458
|
return superThis.isOpen;
|
|
@@ -9656,7 +9477,8 @@ class SelectBox extends Lifecycle {
|
|
|
9656
9477
|
},
|
|
9657
9478
|
selectAll(_evtToken, trigger = true) {
|
|
9658
9479
|
if (bindedOptions.multiple && bindedOptions.maxSelected > 0) {
|
|
9659
|
-
if (superThis.getModelOption().length >
|
|
9480
|
+
if (superThis.getModelOption().length >
|
|
9481
|
+
bindedOptions.maxSelected)
|
|
9660
9482
|
return;
|
|
9661
9483
|
}
|
|
9662
9484
|
if (this.disabled || this.readonly || !bindedOptions.multiple)
|
|
@@ -9688,7 +9510,7 @@ class SelectBox extends Lifecycle {
|
|
|
9688
9510
|
},
|
|
9689
9511
|
deSelectByDataset(_evtToken, dataset, trigger = true) {
|
|
9690
9512
|
if (dataset) {
|
|
9691
|
-
superThis.getModelOption().forEach(optionModel => {
|
|
9513
|
+
superThis.getModelOption().forEach((optionModel) => {
|
|
9692
9514
|
if (optionModel.dataset) {
|
|
9693
9515
|
for (let searchKey in dataset) {
|
|
9694
9516
|
let value = dataset[searchKey];
|
|
@@ -9702,12 +9524,14 @@ class SelectBox extends Lifecycle {
|
|
|
9702
9524
|
this.change(false, trigger);
|
|
9703
9525
|
}
|
|
9704
9526
|
},
|
|
9705
|
-
setValue(_evtToken
|
|
9527
|
+
setValue(_evtToken, value, trigger = true, force = false) {
|
|
9706
9528
|
if (!Array.isArray(value))
|
|
9707
9529
|
value = [value];
|
|
9708
9530
|
value = value.filter((v) => v !== "" && v != null);
|
|
9709
9531
|
if (value.length === 0) {
|
|
9710
|
-
superThis
|
|
9532
|
+
superThis
|
|
9533
|
+
.getModelOption()
|
|
9534
|
+
.forEach((m) => (m.selectedNonTrigger = false));
|
|
9711
9535
|
this.change(false, trigger);
|
|
9712
9536
|
return;
|
|
9713
9537
|
}
|
|
@@ -9721,17 +9545,19 @@ class SelectBox extends Lifecycle {
|
|
|
9721
9545
|
return;
|
|
9722
9546
|
// AJAX: load missing values
|
|
9723
9547
|
if (container.searchController?.isAjax?.()) {
|
|
9548
|
+
container.searchController.resetPagination();
|
|
9549
|
+
superThis.hasLoadedOnce = false;
|
|
9724
9550
|
const { missing } = container.searchController.checkMissingValues(value);
|
|
9725
9551
|
if (missing.length > 0) {
|
|
9726
9552
|
(async () => {
|
|
9727
9553
|
if (bindedOptions.loadingfield)
|
|
9728
9554
|
container.popup?.showLoading?.();
|
|
9729
9555
|
try {
|
|
9730
|
-
container.searchController.resetPagination();
|
|
9731
9556
|
const result = await container.searchController.loadByValues(missing);
|
|
9732
9557
|
if (result.success && result.items.length > 0) {
|
|
9733
9558
|
result.items.forEach((it) => {
|
|
9734
|
-
if (missing.includes(it.value) ||
|
|
9559
|
+
if (missing.includes(it.value) ||
|
|
9560
|
+
missing.includes(it.text))
|
|
9735
9561
|
it.selected = true;
|
|
9736
9562
|
});
|
|
9737
9563
|
container.searchController.applyAjaxResult?.(result.items, false, false);
|
|
@@ -9742,6 +9568,10 @@ class SelectBox extends Lifecycle {
|
|
|
9742
9568
|
}
|
|
9743
9569
|
else if (missing.length > 0) {
|
|
9744
9570
|
console.warn(`Could not load ${missing.length} values:`, missing);
|
|
9571
|
+
setTimeout(() => {
|
|
9572
|
+
container.searchController.resetPagination();
|
|
9573
|
+
this.change(false, trigger);
|
|
9574
|
+
}, 200);
|
|
9745
9575
|
}
|
|
9746
9576
|
}
|
|
9747
9577
|
catch (error) {
|
|
@@ -9770,7 +9600,8 @@ class SelectBox extends Lifecycle {
|
|
|
9770
9600
|
this.change(false, trigger);
|
|
9771
9601
|
},
|
|
9772
9602
|
load() {
|
|
9773
|
-
if ((!superThis.hasLoadedOnce || superThis.isBeforeSearch) &&
|
|
9603
|
+
if ((!superThis.hasLoadedOnce || superThis.isBeforeSearch) &&
|
|
9604
|
+
bindedOptions?.ajax) {
|
|
9774
9605
|
container.searchController.resetPagination();
|
|
9775
9606
|
container.popup.showLoading();
|
|
9776
9607
|
superThis.hasLoadedOnce = true;
|
|
@@ -9816,7 +9647,13 @@ class SelectBox extends Lifecycle {
|
|
|
9816
9647
|
adapter.resetHighlight();
|
|
9817
9648
|
}
|
|
9818
9649
|
this.load();
|
|
9819
|
-
container.popup.open(
|
|
9650
|
+
container.popup.open(() => {
|
|
9651
|
+
setTimeout(() => {
|
|
9652
|
+
if (selectedOption) {
|
|
9653
|
+
adapter.setHighlight(selectedOption, bindedOptions.autoscroll);
|
|
9654
|
+
}
|
|
9655
|
+
}, 100);
|
|
9656
|
+
}, !container.popup.loadingState.isVisible);
|
|
9820
9657
|
container.searchbox.show();
|
|
9821
9658
|
const ViewPanel = container.tags.ViewPanel;
|
|
9822
9659
|
ViewPanel.setAttribute("aria-expanded", "true");
|
|
@@ -9857,9 +9694,10 @@ class SelectBox extends Lifecycle {
|
|
|
9857
9694
|
else
|
|
9858
9695
|
this.open();
|
|
9859
9696
|
},
|
|
9860
|
-
change(_evtToken
|
|
9697
|
+
change(_evtToken, canTrigger = true) {
|
|
9861
9698
|
if (canTrigger) {
|
|
9862
|
-
if (bindedOptions.multiple &&
|
|
9699
|
+
if (bindedOptions.multiple &&
|
|
9700
|
+
bindedOptions.maxSelected > 0) {
|
|
9863
9701
|
if (this.valueArray.length > bindedOptions.maxSelected) {
|
|
9864
9702
|
this.setValue(null, this.oldValue, false, true);
|
|
9865
9703
|
}
|
|
@@ -9894,7 +9732,8 @@ class SelectBox extends Lifecycle {
|
|
|
9894
9732
|
},
|
|
9895
9733
|
refreshMask() {
|
|
9896
9734
|
let mask = bindedOptions.placeholder;
|
|
9897
|
-
if (!bindedOptions.multiple &&
|
|
9735
|
+
if (!bindedOptions.multiple &&
|
|
9736
|
+
superThis.getModelOption().length > 0) {
|
|
9898
9737
|
mask = this.mask[0];
|
|
9899
9738
|
}
|
|
9900
9739
|
mask ?? (mask = bindedOptions.placeholder);
|
|
@@ -9926,7 +9765,9 @@ class SelectBox extends Lifecycle {
|
|
|
9926
9765
|
.search("")
|
|
9927
9766
|
.then(() => {
|
|
9928
9767
|
container.popup?.triggerResize?.();
|
|
9929
|
-
|
|
9768
|
+
setTimeout(() => {
|
|
9769
|
+
resove(getInstance());
|
|
9770
|
+
}, 60);
|
|
9930
9771
|
})
|
|
9931
9772
|
.catch((err) => {
|
|
9932
9773
|
console.error("Initial ajax load error:", err);
|
|
@@ -9976,7 +9817,8 @@ class SelectBox extends Lifecycle {
|
|
|
9976
9817
|
set(value) {
|
|
9977
9818
|
superThis[privateProp] = !!value;
|
|
9978
9819
|
if (superThis.container?.targetElement?.dataset) {
|
|
9979
|
-
superThis.container.targetElement.dataset[prop] =
|
|
9820
|
+
superThis.container.targetElement.dataset[prop] =
|
|
9821
|
+
String(!!value);
|
|
9980
9822
|
}
|
|
9981
9823
|
},
|
|
9982
9824
|
enumerable: true,
|
|
@@ -10004,7 +9846,7 @@ class SelectBox extends Lifecycle {
|
|
|
10004
9846
|
* @returns A flat array of option models (possibly filtered).
|
|
10005
9847
|
* @internal
|
|
10006
9848
|
*/
|
|
10007
|
-
getModelOption(isSelected
|
|
9849
|
+
getModelOption(isSelected) {
|
|
10008
9850
|
if (!this.optionModelManager)
|
|
10009
9851
|
return [];
|
|
10010
9852
|
const { modelList } = this.optionModelManager.getResources();
|
|
@@ -10084,14 +9926,6 @@ class ElementAdditionObserver {
|
|
|
10084
9926
|
* @internal
|
|
10085
9927
|
*/
|
|
10086
9928
|
this.isActive = false;
|
|
10087
|
-
/**
|
|
10088
|
-
* Underlying DOM {@link MutationObserver} instance.
|
|
10089
|
-
*
|
|
10090
|
-
* `null` when disconnected.
|
|
10091
|
-
*
|
|
10092
|
-
* @internal
|
|
10093
|
-
*/
|
|
10094
|
-
this.observer = null;
|
|
10095
9929
|
/**
|
|
10096
9930
|
* Registered detection callbacks.
|
|
10097
9931
|
*
|
|
@@ -10685,7 +10519,7 @@ class Selective extends Lifecycle {
|
|
|
10685
10519
|
if (wasObserving)
|
|
10686
10520
|
this.EAObserver?.disconnect();
|
|
10687
10521
|
bindMap.self?.deInit?.();
|
|
10688
|
-
const wrapper =
|
|
10522
|
+
const wrapper = bindMap.container?.element ?? selectElement.parentElement;
|
|
10689
10523
|
selectElement.style.display = "";
|
|
10690
10524
|
selectElement.style.visibility = "";
|
|
10691
10525
|
selectElement.disabled = false;
|
|
@@ -10905,7 +10739,7 @@ const SECLASS = new Selective();
|
|
|
10905
10739
|
*
|
|
10906
10740
|
* Declared as `const` literal type to enable strict typing and easy tree-shaking.
|
|
10907
10741
|
*/
|
|
10908
|
-
const version = "1.4.
|
|
10742
|
+
const version = "1.4.2";
|
|
10909
10743
|
/**
|
|
10910
10744
|
* Library name identifier.
|
|
10911
10745
|
*
|
|
@@ -10951,7 +10785,7 @@ function find(query) {
|
|
|
10951
10785
|
* // Destroy all instances
|
|
10952
10786
|
* destroy();
|
|
10953
10787
|
*/
|
|
10954
|
-
function destroy(query
|
|
10788
|
+
function destroy(query) {
|
|
10955
10789
|
SECLASS.destroy(query);
|
|
10956
10790
|
}
|
|
10957
10791
|
/**
|