selective-ui 1.4.1 → 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 +8 -2
- package/dist/selective-ui.css.map +1 -1
- package/dist/selective-ui.esm.js +158 -23
- 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 +159 -24
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/css/views/option-view.css +2 -2
- package/src/ts/adapter/mixed-adapter.ts +2 -3
- package/src/ts/components/selectbox.ts +19 -4
- package/src/ts/core/base/adapter.ts +24 -2
- package/src/ts/core/base/virtual-recyclerview.ts +89 -8
- package/src/ts/models/option-model.ts +28 -1
- package/src/ts/services/refresher.ts +7 -1
package/dist/selective-ui.umd.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! Selective UI v1.4.
|
|
1
|
+
/*! Selective UI v1.4.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) :
|
|
@@ -978,8 +978,9 @@
|
|
|
978
978
|
*
|
|
979
979
|
* @param select - Native `<select>` element used as the sizing reference and option source.
|
|
980
980
|
* @param view - View panel element whose inline styles will be updated.
|
|
981
|
+
* @param isWidthOnly - If true, only the width will be updated; height will be left unchanged.
|
|
981
982
|
*/
|
|
982
|
-
static resizeBox(select, view) {
|
|
983
|
+
static resizeBox(select, view, isWidthOnly = false) {
|
|
983
984
|
const bindedMap = Libs.getBinderMap(select);
|
|
984
985
|
if (!bindedMap?.options)
|
|
985
986
|
return;
|
|
@@ -999,7 +1000,12 @@
|
|
|
999
1000
|
width = options.width;
|
|
1000
1001
|
if (cfgHeight > 0)
|
|
1001
1002
|
height = options.height;
|
|
1002
|
-
|
|
1003
|
+
if (isWidthOnly) {
|
|
1004
|
+
Object.assign(view.style, { width, maxWidth: width, minWidth });
|
|
1005
|
+
}
|
|
1006
|
+
else {
|
|
1007
|
+
Object.assign(view.style, { width, height, maxWidth: width, minWidth, minHeight });
|
|
1008
|
+
}
|
|
1003
1009
|
}
|
|
1004
1010
|
}
|
|
1005
1011
|
|
|
@@ -4023,6 +4029,32 @@
|
|
|
4023
4029
|
}
|
|
4024
4030
|
iEvents.callEvent([this, value], ...this.privOnInternalSelected);
|
|
4025
4031
|
}
|
|
4032
|
+
/**
|
|
4033
|
+
* Resolved display mask for this option.
|
|
4034
|
+
*
|
|
4035
|
+
* The mask is the primary render label used by the UI layer and supports
|
|
4036
|
+
* optional inline tag translation / rich HTML rendering.
|
|
4037
|
+
*
|
|
4038
|
+
* Source priority:
|
|
4039
|
+
* 1. `data-mask` (`dataset.mask`)
|
|
4040
|
+
* 2. Native `<option>` text content (`targetElement.text`)
|
|
4041
|
+
*
|
|
4042
|
+
* Processing pipeline:
|
|
4043
|
+
* - Raw content is first passed through {@link Libs.tagTranslate}.
|
|
4044
|
+
* - When `options.allowHtml === true`, translated HTML is preserved.
|
|
4045
|
+
* - Otherwise, all markup is stripped via {@link Libs.stripHtml}.
|
|
4046
|
+
*
|
|
4047
|
+
* Unlike {@link text}, this getter prioritizes the custom dataset mask,
|
|
4048
|
+
* making it suitable for display overrides without mutating the native
|
|
4049
|
+
* `<option>` label.
|
|
4050
|
+
*
|
|
4051
|
+
* @returns {string} Render-ready option label.
|
|
4052
|
+
*/
|
|
4053
|
+
get mask() {
|
|
4054
|
+
const raw = this.dataset?.mask ?? this.targetElement?.text ?? "";
|
|
4055
|
+
const translated = Libs.tagTranslate(raw);
|
|
4056
|
+
return this.options.allowHtml ? translated : Libs.stripHtml(translated);
|
|
4057
|
+
}
|
|
4026
4058
|
/**
|
|
4027
4059
|
* Display label for rendering (with tag translation and HTML policy).
|
|
4028
4060
|
*
|
|
@@ -4036,7 +4068,7 @@
|
|
|
4036
4068
|
* @returns {string}
|
|
4037
4069
|
*/
|
|
4038
4070
|
get text() {
|
|
4039
|
-
const raw = this.
|
|
4071
|
+
const raw = this.targetElement?.text ?? this.dataset?.mask ?? "";
|
|
4040
4072
|
const translated = Libs.tagTranslate(raw);
|
|
4041
4073
|
return this.options.allowHtml ? translated : Libs.stripHtml(translated);
|
|
4042
4074
|
}
|
|
@@ -5944,6 +5976,16 @@
|
|
|
5944
5976
|
* This flag is intentionally generic and is coordinated by higher-level components.
|
|
5945
5977
|
*/
|
|
5946
5978
|
this.isSkipEvent = false;
|
|
5979
|
+
/**
|
|
5980
|
+
* Tracks all scheduler keys registered by this adapter instance via
|
|
5981
|
+
* {@link onPropChanging} and {@link onPropChanged}.
|
|
5982
|
+
*
|
|
5983
|
+
* Used during {@link destroy} to clean up all associated pipelines
|
|
5984
|
+
* from the global {@link Libs.callbackScheduler}.
|
|
5985
|
+
*
|
|
5986
|
+
* Keys are deduplicated automatically by Set semantics.
|
|
5987
|
+
*/
|
|
5988
|
+
this.callbackSchedulerList = new Set();
|
|
5947
5989
|
this.items = items;
|
|
5948
5990
|
this.init();
|
|
5949
5991
|
}
|
|
@@ -5985,7 +6027,9 @@
|
|
|
5985
6027
|
* @see {@link changingProp}
|
|
5986
6028
|
*/
|
|
5987
6029
|
onPropChanging(propName, callback) {
|
|
5988
|
-
|
|
6030
|
+
const key = `${propName}ing_${this.adapterKey}`;
|
|
6031
|
+
Libs.callbackScheduler.on(key, callback, { debounce: 0 });
|
|
6032
|
+
this.callbackSchedulerList.add(key);
|
|
5989
6033
|
}
|
|
5990
6034
|
/**
|
|
5991
6035
|
* Registers a **post-change** callback for a property pipeline.
|
|
@@ -6001,9 +6045,11 @@
|
|
|
6001
6045
|
* @see {@link changeProp}
|
|
6002
6046
|
*/
|
|
6003
6047
|
onPropChanged(propName, callback) {
|
|
6004
|
-
|
|
6048
|
+
const key = `${propName}_${this.adapterKey}`;
|
|
6049
|
+
Libs.callbackScheduler.on(key, callback, {
|
|
6005
6050
|
debounce: 0,
|
|
6006
6051
|
});
|
|
6052
|
+
this.callbackSchedulerList.add(key);
|
|
6007
6053
|
}
|
|
6008
6054
|
/**
|
|
6009
6055
|
* Triggers the **post-change** pipeline for a given property.
|
|
@@ -6144,11 +6190,16 @@
|
|
|
6144
6190
|
if (this.is(LifecycleState.DESTROYED)) {
|
|
6145
6191
|
return;
|
|
6146
6192
|
}
|
|
6193
|
+
this.callbackSchedulerList.forEach((key) => {
|
|
6194
|
+
Libs.callbackScheduler.off(key);
|
|
6195
|
+
});
|
|
6196
|
+
this.callbackSchedulerList.clear();
|
|
6147
6197
|
this.recyclerView = null;
|
|
6148
6198
|
this.items.forEach((item) => {
|
|
6149
6199
|
item?.destroy?.();
|
|
6150
6200
|
});
|
|
6151
6201
|
this.items = [];
|
|
6202
|
+
super.destroy();
|
|
6152
6203
|
}
|
|
6153
6204
|
}
|
|
6154
6205
|
|
|
@@ -7448,7 +7499,7 @@
|
|
|
7448
7499
|
* @returns {void}
|
|
7449
7500
|
*/
|
|
7450
7501
|
resetHighlight() {
|
|
7451
|
-
this.setHighlight(0);
|
|
7502
|
+
this.setHighlight(0, false);
|
|
7452
7503
|
}
|
|
7453
7504
|
/**
|
|
7454
7505
|
* Moves highlight among **visible** options and optionally scrolls the new target into view.
|
|
@@ -7541,11 +7592,10 @@
|
|
|
7541
7592
|
this.currentHighlightIndex = i;
|
|
7542
7593
|
if (isScrollToView) {
|
|
7543
7594
|
const el = item.view?.getView?.();
|
|
7544
|
-
if (el) {
|
|
7595
|
+
if (el?.isConnected) {
|
|
7545
7596
|
el.scrollIntoView({ block: "center", behavior: "smooth" });
|
|
7546
7597
|
}
|
|
7547
7598
|
else {
|
|
7548
|
-
// If virtualized, ensure the item is rendered before trying to scroll.
|
|
7549
7599
|
this.recyclerView?.ensureRendered?.(i, {
|
|
7550
7600
|
scrollIntoView: true,
|
|
7551
7601
|
});
|
|
@@ -7821,6 +7871,12 @@
|
|
|
7821
7871
|
this.lastRenderCount = 0;
|
|
7822
7872
|
this.suspended = false;
|
|
7823
7873
|
this.resumeResizeAfter = false;
|
|
7874
|
+
/**
|
|
7875
|
+
* When set, scrollToIndex() will be called after the next measureVisibleAndUpdate()
|
|
7876
|
+
* completes and Fenwick has been updated with real heights.
|
|
7877
|
+
* Set by ensureRendered() and cleared after the corrective scroll fires.
|
|
7878
|
+
*/
|
|
7879
|
+
this.pendingScrollToIndex = null;
|
|
7824
7880
|
/** Small cache for sticky header height (≈16ms TTL) to limit layout reads. */
|
|
7825
7881
|
this.stickyCacheTick = 0;
|
|
7826
7882
|
this.stickyCacheVal = 0;
|
|
@@ -7987,9 +8043,19 @@
|
|
|
7987
8043
|
* @returns {void}
|
|
7988
8044
|
*/
|
|
7989
8045
|
ensureRendered(index, opt) {
|
|
7990
|
-
|
|
7991
|
-
|
|
7992
|
-
this.
|
|
8046
|
+
if (!opt?.scrollIntoView) {
|
|
8047
|
+
// No scroll requested — mount only (legacy path, used by probes).
|
|
8048
|
+
this.mountRange(index, index);
|
|
8049
|
+
return;
|
|
8050
|
+
}
|
|
8051
|
+
// Pass 1: instant — brings window to vicinity, triggers measure.
|
|
8052
|
+
// Must be instant so Pass 2 smooth scroll isn't interrupted mid-animation.
|
|
8053
|
+
this.scrollToIndex(index, "instant");
|
|
8054
|
+
// Pass 2: measureVisibleAndUpdate() will consume this and fire a corrective
|
|
8055
|
+
// smooth scroll after Fenwick has been updated with real heights.
|
|
8056
|
+
// rv.resume() is guaranteed to run before this callback (popup.open onComplete
|
|
8057
|
+
// calls rv.resume() first), so the window is already rendered when we arrive here.
|
|
8058
|
+
this.pendingScrollToIndex = index;
|
|
7993
8059
|
}
|
|
7994
8060
|
/**
|
|
7995
8061
|
* Scrolls the scroll container to align the item at `index` into view.
|
|
@@ -8003,15 +8069,25 @@
|
|
|
8003
8069
|
* @param {number} index - Item index to bring into view.
|
|
8004
8070
|
* @returns {void}
|
|
8005
8071
|
*/
|
|
8006
|
-
scrollToIndex(index) {
|
|
8072
|
+
scrollToIndex(index, behavior = "smooth") {
|
|
8007
8073
|
const count = this.adapter?.itemCount?.() ?? 0;
|
|
8008
8074
|
if (count <= 0)
|
|
8009
8075
|
return;
|
|
8010
8076
|
const topInContainer = this.offsetTopOf(index);
|
|
8011
8077
|
const containerTop = this.containerTopInScroll();
|
|
8012
|
-
const
|
|
8078
|
+
const stickyH = this.stickyTopHeight();
|
|
8079
|
+
const viewportH = Math.max(0, this.scrollEl.clientHeight - stickyH);
|
|
8080
|
+
// item height from cache, or current estimate for unmeasured items
|
|
8081
|
+
const est = this.getEstimate();
|
|
8082
|
+
const itemH = this.heightCache[index] ?? est;
|
|
8083
|
+
// Align item center to viewport center (below any sticky header).
|
|
8084
|
+
// viewportH already excludes stickyH, so no further offset needed.
|
|
8085
|
+
// Equivalent to scrollIntoView({ block: "center" }).
|
|
8086
|
+
const centeredTarget = containerTop + topInContainer
|
|
8087
|
+
- (viewportH - itemH) / 3;
|
|
8013
8088
|
const maxScroll = Math.max(0, this.scrollEl.scrollHeight - this.scrollEl.clientHeight);
|
|
8014
|
-
|
|
8089
|
+
const clamped = Math.min(Math.max(0, centeredTarget), maxScroll);
|
|
8090
|
+
this.scrollEl.scrollTo({ top: clamped, behavior });
|
|
8015
8091
|
}
|
|
8016
8092
|
/**
|
|
8017
8093
|
* Disposes runtime resources without destroying the instance.
|
|
@@ -8078,9 +8154,22 @@
|
|
|
8078
8154
|
if (count <= 0)
|
|
8079
8155
|
return;
|
|
8080
8156
|
this.suspend();
|
|
8081
|
-
this.
|
|
8157
|
+
this.pendingScrollToIndex = null;
|
|
8158
|
+
// When visibility changes (search filter applied or cleared), heightCache may
|
|
8159
|
+
// contain heights measured while only a subset of items was visible. Re-using
|
|
8160
|
+
// these partial measurements in rebuildFenwick() causes incorrect prefix sums
|
|
8161
|
+
// (e.g. items measured while scrolled into a filtered window have real heights,
|
|
8162
|
+
// while surrounding items still use estimates — creating an uneven Fenwick).
|
|
8163
|
+
//
|
|
8164
|
+
// Safe fix: clear heightCache entirely on visibility change. The adaptive
|
|
8165
|
+
// estimator will re-seed from probeInitialHeight() on the next render, and
|
|
8166
|
+
// items will be re-measured as they scroll into view.
|
|
8167
|
+
this.heightCache = [];
|
|
8168
|
+
this.measuredSum = 0;
|
|
8169
|
+
this.measuredCount = 0;
|
|
8170
|
+
this.firstMeasured = false;
|
|
8171
|
+
this.resetDOM();
|
|
8082
8172
|
this.cleanupInvisibleItems();
|
|
8083
|
-
this.recomputeMeasuredStats(count);
|
|
8084
8173
|
this.rebuildFenwick(count);
|
|
8085
8174
|
this.start = 0;
|
|
8086
8175
|
this.end = -1;
|
|
@@ -8098,7 +8187,30 @@
|
|
|
8098
8187
|
}
|
|
8099
8188
|
}
|
|
8100
8189
|
/**
|
|
8101
|
-
* Resets
|
|
8190
|
+
* Resets DOM nodes, Fenwick sums, padding, and estimator stats — but preserves {@link heightCache}.
|
|
8191
|
+
*
|
|
8192
|
+
* Use this inside {@link refreshItem} so that {@link recomputeMeasuredStats} can still
|
|
8193
|
+
* read previously measured heights before the Fenwick tree is rebuilt.
|
|
8194
|
+
*
|
|
8195
|
+
* DOM side effects:
|
|
8196
|
+
* - Removes all currently mounted item elements tracked in {@link created}.
|
|
8197
|
+
* - Resets pad heights to `0px`.
|
|
8198
|
+
*
|
|
8199
|
+
* @returns {void}
|
|
8200
|
+
*/
|
|
8201
|
+
resetDOM() {
|
|
8202
|
+
this.created.forEach((el) => el.remove());
|
|
8203
|
+
this.created.clear();
|
|
8204
|
+
this.fenwick.reset(0);
|
|
8205
|
+
this.PadTop.style.height = "0px";
|
|
8206
|
+
this.PadBottom.style.height = "0px";
|
|
8207
|
+
this.firstMeasured = false;
|
|
8208
|
+
}
|
|
8209
|
+
/**
|
|
8210
|
+
* Full reset: clears DOM nodes, Fenwick sums, padding, estimator stats, AND {@link heightCache}.
|
|
8211
|
+
*
|
|
8212
|
+
* Use this for complete teardown (e.g., adapter swap, destroy sequence) where all
|
|
8213
|
+
* cached measurements should be discarded.
|
|
8102
8214
|
*
|
|
8103
8215
|
* DOM side effects:
|
|
8104
8216
|
* - Removes all currently mounted item elements tracked in {@link created}.
|
|
@@ -8452,6 +8564,15 @@
|
|
|
8452
8564
|
this.rebuildFenwick(count);
|
|
8453
8565
|
this.scheduleUpdateWindow();
|
|
8454
8566
|
}
|
|
8567
|
+
// Corrective scroll: if ensureRendered() registered a target index, fire
|
|
8568
|
+
// scrollToIndex() now that real heights are in Fenwick. Clear the target
|
|
8569
|
+
// first to prevent infinite re-triggering (scrollToIndex may cause another
|
|
8570
|
+
// measure cycle, but heights won't change so changed === false next time).
|
|
8571
|
+
if (this.pendingScrollToIndex !== null) {
|
|
8572
|
+
const target = this.pendingScrollToIndex;
|
|
8573
|
+
this.pendingScrollToIndex = null;
|
|
8574
|
+
this.scrollToIndex(target, "smooth");
|
|
8575
|
+
}
|
|
8455
8576
|
}
|
|
8456
8577
|
/**
|
|
8457
8578
|
* Scroll event handler. Schedules a window update on the next frame.
|
|
@@ -9007,6 +9128,7 @@
|
|
|
9007
9128
|
e.preventDefault();
|
|
9008
9129
|
});
|
|
9009
9130
|
Refresher.resizeBox(select, container.tags.ViewPanel);
|
|
9131
|
+
Refresher.resizeBox(select, this.node, true);
|
|
9010
9132
|
select.classList.add("init");
|
|
9011
9133
|
// initial mask
|
|
9012
9134
|
const action = this.getAction();
|
|
@@ -9322,7 +9444,7 @@
|
|
|
9322
9444
|
get mask() {
|
|
9323
9445
|
const item_list = [];
|
|
9324
9446
|
superThis.getModelOption(true).forEach((m) => {
|
|
9325
|
-
item_list.push(m.
|
|
9447
|
+
item_list.push(m.mask);
|
|
9326
9448
|
});
|
|
9327
9449
|
return item_list;
|
|
9328
9450
|
},
|
|
@@ -9429,13 +9551,14 @@
|
|
|
9429
9551
|
return;
|
|
9430
9552
|
// AJAX: load missing values
|
|
9431
9553
|
if (container.searchController?.isAjax?.()) {
|
|
9554
|
+
container.searchController.resetPagination();
|
|
9555
|
+
superThis.hasLoadedOnce = false;
|
|
9432
9556
|
const { missing } = container.searchController.checkMissingValues(value);
|
|
9433
9557
|
if (missing.length > 0) {
|
|
9434
9558
|
(async () => {
|
|
9435
9559
|
if (bindedOptions.loadingfield)
|
|
9436
9560
|
container.popup?.showLoading?.();
|
|
9437
9561
|
try {
|
|
9438
|
-
container.searchController.resetPagination();
|
|
9439
9562
|
const result = await container.searchController.loadByValues(missing);
|
|
9440
9563
|
if (result.success && result.items.length > 0) {
|
|
9441
9564
|
result.items.forEach((it) => {
|
|
@@ -9451,6 +9574,10 @@
|
|
|
9451
9574
|
}
|
|
9452
9575
|
else if (missing.length > 0) {
|
|
9453
9576
|
console.warn(`Could not load ${missing.length} values:`, missing);
|
|
9577
|
+
setTimeout(() => {
|
|
9578
|
+
container.searchController.resetPagination();
|
|
9579
|
+
this.change(false, trigger);
|
|
9580
|
+
}, 200);
|
|
9454
9581
|
}
|
|
9455
9582
|
}
|
|
9456
9583
|
catch (error) {
|
|
@@ -9526,7 +9653,13 @@
|
|
|
9526
9653
|
adapter.resetHighlight();
|
|
9527
9654
|
}
|
|
9528
9655
|
this.load();
|
|
9529
|
-
container.popup.open(
|
|
9656
|
+
container.popup.open(() => {
|
|
9657
|
+
setTimeout(() => {
|
|
9658
|
+
if (selectedOption) {
|
|
9659
|
+
adapter.setHighlight(selectedOption, bindedOptions.autoscroll);
|
|
9660
|
+
}
|
|
9661
|
+
}, 100);
|
|
9662
|
+
}, !container.popup.loadingState.isVisible);
|
|
9530
9663
|
container.searchbox.show();
|
|
9531
9664
|
const ViewPanel = container.tags.ViewPanel;
|
|
9532
9665
|
ViewPanel.setAttribute("aria-expanded", "true");
|
|
@@ -9638,7 +9771,9 @@
|
|
|
9638
9771
|
.search("")
|
|
9639
9772
|
.then(() => {
|
|
9640
9773
|
container.popup?.triggerResize?.();
|
|
9641
|
-
|
|
9774
|
+
setTimeout(() => {
|
|
9775
|
+
resove(getInstance());
|
|
9776
|
+
}, 60);
|
|
9642
9777
|
})
|
|
9643
9778
|
.catch((err) => {
|
|
9644
9779
|
console.error("Initial ajax load error:", err);
|
|
@@ -10607,7 +10742,7 @@
|
|
|
10607
10742
|
if (typeof globalThis.GLOBAL_SEUI == "undefined") {
|
|
10608
10743
|
const SECLASS = new Selective();
|
|
10609
10744
|
globalThis.GLOBAL_SEUI = {
|
|
10610
|
-
version: "1.4.
|
|
10745
|
+
version: "1.4.2",
|
|
10611
10746
|
name: "SelectiveUI",
|
|
10612
10747
|
bind: SECLASS.bind.bind(SECLASS),
|
|
10613
10748
|
find: SECLASS.find.bind(SECLASS),
|
|
@@ -10640,7 +10775,7 @@
|
|
|
10640
10775
|
init();
|
|
10641
10776
|
}
|
|
10642
10777
|
}
|
|
10643
|
-
console.log(`[${"SelectiveUI"}] v${"1.4.
|
|
10778
|
+
console.log(`[${"SelectiveUI"}] v${"1.4.2"} loaded successfully`);
|
|
10644
10779
|
}
|
|
10645
10780
|
else {
|
|
10646
10781
|
console.warn(`[${globalThis.GLOBAL_SEUI.name}] Already loaded (v${globalThis.GLOBAL_SEUI.version}). ` +
|