selective-ui 1.2.0 → 1.2.1

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.
@@ -1,4 +1,4 @@
1
- /*! Selective UI v1.2.0 | MIT License */
1
+ /*! Selective UI v1.2.1 | MIT License */
2
2
  /**
3
3
  * @class
4
4
  */
@@ -2079,8 +2079,7 @@ class EffectorImpl {
2079
2079
  resize(config) {
2080
2080
  if (!this.element)
2081
2081
  return this;
2082
- if (this._resizeTimeout)
2083
- clearTimeout(this._resizeTimeout);
2082
+ this.cancel();
2084
2083
  const { duration = 200, width, left, top, maxHeight, realHeight, position = "bottom", animate = true, onComplete, } = config;
2085
2084
  const currentPosition = this.element.classList.contains("position-top") ? "top" : "bottom";
2086
2085
  const isPositionChanged = currentPosition !== position;
@@ -2090,7 +2089,7 @@ class EffectorImpl {
2090
2089
  if (isPositionChanged) {
2091
2090
  this.element.style.transition = `top ${duration}ms ease-out, height ${duration}ms ease-out, max-height ${duration}ms ease-out;`;
2092
2091
  }
2093
- requestAnimationFrame(() => {
2092
+ setTimeout(() => {
2094
2093
  const styles = {
2095
2094
  width: `${width}px`,
2096
2095
  left: `${left}px`,
@@ -2106,14 +2105,14 @@ class EffectorImpl {
2106
2105
  else {
2107
2106
  this._resizeTimeout = setTimeout(() => {
2108
2107
  if (this.element?.style) {
2109
- this.element.style.transition = "none";
2108
+ this.element.style.transition = null;
2110
2109
  }
2111
2110
  }, duration);
2112
2111
  }
2113
2112
  Object.assign(this.element.style, styles);
2114
2113
  if (animate && (isPositionChanged || heightDiff > 1)) {
2115
2114
  this._resizeTimeout = setTimeout(() => {
2116
- this.element.style.transition = "none";
2115
+ this.element.style.transition = null;
2117
2116
  if (isPositionChanged)
2118
2117
  delete this.element.style.transition;
2119
2118
  onComplete?.();
@@ -2124,7 +2123,7 @@ class EffectorImpl {
2124
2123
  delete this.element.style.transition;
2125
2124
  onComplete?.();
2126
2125
  }
2127
- });
2126
+ }, 20);
2128
2127
  return this;
2129
2128
  }
2130
2129
  /**
@@ -2319,17 +2318,28 @@ class GroupModel extends Model {
2319
2318
  }
2320
2319
 
2321
2320
  /**
2322
- * @extends {Model<HTMLOptionElement, OptionViewTags, OptionView>}
2321
+ * @extends {Model<HTMLOptionElement, OptionViewTags, OptionView, SelectiveOptions>}
2323
2322
  */
2324
2323
  class OptionModel extends Model {
2325
- constructor() {
2326
- super(...arguments);
2324
+ /**
2325
+ * Constructs a Model instance with configuration options and optional bindings to a target element and view.
2326
+ * Stores references for later updates and rendering.
2327
+ *
2328
+ * @param {SelectiveOptions} options - Configuration options for the model.
2329
+ * @param {HTMLOptionElement|null} [targetElement=null] - The underlying element (e.g., <option> or group node).
2330
+ * @param {OptionView|null} [view=null] - The associated view responsible for rendering the model.
2331
+ */
2332
+ constructor(options, targetElement = null, view = null) {
2333
+ super(options, targetElement, view);
2327
2334
  this._privOnSelected = [];
2328
2335
  this._privOnInternalSelected = [];
2329
2336
  this._privOnVisibilityChanged = [];
2330
2337
  this._visible = true;
2331
2338
  this._highlighted = false;
2332
2339
  this.group = null;
2340
+ (async () => {
2341
+ this.textToFind = Libs.string2normalize(this.textContent.toLowerCase());
2342
+ })();
2333
2343
  }
2334
2344
  /**
2335
2345
  * Returns the image source from dataset (imgsrc or image), or an empty string if absent.
@@ -2503,6 +2513,7 @@ class OptionModel extends Model {
2503
2513
  * and synchronizes initial selected state to the view.
2504
2514
  */
2505
2515
  onTargetChanged() {
2516
+ this.textToFind = Libs.string2normalize(this.textContent.toLowerCase());
2506
2517
  if (!this.view)
2507
2518
  return;
2508
2519
  const labelContent = this.view.view.tags.LabelContent;
@@ -3200,9 +3211,7 @@ class SearchController {
3200
3211
  }
3201
3212
  let hasVisibleItems = false;
3202
3213
  flatOptions.forEach((opt) => {
3203
- const text = String(opt.textContent ?? opt.text ?? "").toLowerCase();
3204
- const textNA = Libs.string2normalize(text);
3205
- const isVisible = lower === "" || text.includes(lower) || textNA.includes(lowerNA);
3214
+ const isVisible = lower === "" || opt.textToFind.includes(lowerNA);
3206
3215
  opt.visible = isVisible;
3207
3216
  if (isVisible)
3208
3217
  hasVisibleItems = true;
@@ -4172,6 +4181,19 @@ class MixedAdapter extends Adapter {
4172
4181
  this.groups = [];
4173
4182
  this.flatOptions = [];
4174
4183
  this._buildFlatStructure();
4184
+ Libs.callbackScheduler.on(`sche_vis_${this.adapterKey}`, () => {
4185
+ const visibleCount = this.flatOptions.filter((item) => item.visible).length;
4186
+ const totalCount = this.flatOptions.length;
4187
+ this._visibilityChangedCallbacks.forEach((callback) => {
4188
+ callback({
4189
+ visibleCount,
4190
+ totalCount,
4191
+ hasVisible: visibleCount > 0,
4192
+ isEmpty: totalCount === 0,
4193
+ });
4194
+ });
4195
+ Libs.callbackScheduler.run(`sche_vis_proxy_${this.adapterKey}`);
4196
+ }, { debounce: 10 });
4175
4197
  }
4176
4198
  /**
4177
4199
  * Build flat list of all options for navigation
@@ -4407,16 +4429,7 @@ class MixedAdapter extends Adapter {
4407
4429
  * Computes visible and total counts, then emits aggregated state.
4408
4430
  */
4409
4431
  _notifyVisibilityChanged() {
4410
- const visibleCount = this.flatOptions.filter((item) => item.visible).length;
4411
- const totalCount = this.flatOptions.length;
4412
- this._visibilityChangedCallbacks.forEach((callback) => {
4413
- callback({
4414
- visibleCount,
4415
- totalCount,
4416
- hasVisible: visibleCount > 0,
4417
- isEmpty: totalCount === 0,
4418
- });
4419
- });
4432
+ Libs.callbackScheduler.run(`sche_vis_${this.adapterKey}`);
4420
4433
  }
4421
4434
  /**
4422
4435
  * Computes and returns current visibility statistics for options.
@@ -5128,6 +5141,8 @@ class VirtualRecyclerView extends RecyclerView {
5128
5141
  return;
5129
5142
  }
5130
5143
  const item = this.adapter.items[index];
5144
+ if (!item)
5145
+ return;
5131
5146
  const existing = this.created.get(index);
5132
5147
  if (existing) {
5133
5148
  if (!item?.view) {
@@ -5385,6 +5400,7 @@ class SelectBox {
5385
5400
  searchController.setAjax(options.ajax);
5386
5401
  }
5387
5402
  const optionAdapter = container.popup.optionAdapter;
5403
+ let hightlightTimer = null;
5388
5404
  const searchHandle = (keyword, isTrigger) => {
5389
5405
  if (!isTrigger && keyword === "") {
5390
5406
  searchController.clear();
@@ -5395,13 +5411,16 @@ class SelectBox {
5395
5411
  searchController
5396
5412
  .search(keyword)
5397
5413
  .then((result) => {
5398
- container.popup?.triggerResize?.();
5399
- if (result?.hasResults) {
5400
- setTimeout(() => {
5401
- container.popup?.triggerResize?.();
5402
- optionAdapter.resetHighlight();
5403
- }, options.animationtime ? options.animationtime + 10 : 0);
5404
- }
5414
+ clearTimeout(hightlightTimer);
5415
+ Libs.callbackScheduler.on(`sche_vis_proxy_${optionAdapter.adapterKey}`, () => {
5416
+ container.popup?.triggerResize?.();
5417
+ if (result?.hasResults) {
5418
+ hightlightTimer = setTimeout(() => {
5419
+ optionAdapter.resetHighlight();
5420
+ container.popup?.triggerResize?.();
5421
+ }, options.animationtime ?? 0);
5422
+ }
5423
+ }, { debounce: 10 });
5405
5424
  })
5406
5425
  .catch((error) => {
5407
5426
  console.error("Search error:", error);
@@ -5412,16 +5431,18 @@ class SelectBox {
5412
5431
  searchbox.onSearch = (keyword, isTrigger) => {
5413
5432
  if (!searchController.compareSearchTrigger(keyword))
5414
5433
  return;
5434
+ if (searchHandleTimer)
5435
+ clearTimeout(searchHandleTimer);
5415
5436
  if (searchController.isAjax()) {
5416
- if (searchHandleTimer)
5417
- clearTimeout(searchHandleTimer);
5418
5437
  container.popup?.showLoading?.();
5419
5438
  searchHandleTimer = setTimeout(() => {
5420
5439
  searchHandle(keyword, isTrigger);
5421
5440
  }, options.delaysearchtime ?? 0);
5422
5441
  }
5423
5442
  else {
5424
- searchHandle(keyword, isTrigger);
5443
+ searchHandleTimer = setTimeout(() => {
5444
+ searchHandle(keyword, isTrigger);
5445
+ }, 10);
5425
5446
  }
5426
5447
  };
5427
5448
  searchController.setPopup(container.popup);
@@ -6232,7 +6253,7 @@ const SECLASS = new Selective();
6232
6253
  *
6233
6254
  * Declared as `const` literal type to enable strict typing and easy tree-shaking.
6234
6255
  */
6235
- const version = "1.2.0";
6256
+ const version = "1.2.1";
6236
6257
  /**
6237
6258
  * Library name identifier.
6238
6259
  *