selective-ui 1.4.0 → 1.4.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.
Files changed (70) hide show
  1. package/dist/selective-ui.css +0 -6
  2. package/dist/selective-ui.css.map +1 -1
  3. package/dist/selective-ui.esm.js +252 -553
  4. package/dist/selective-ui.esm.js.map +1 -1
  5. package/dist/selective-ui.esm.min.js +2 -2
  6. package/dist/selective-ui.esm.min.js.br +0 -0
  7. package/dist/selective-ui.min.css +1 -1
  8. package/dist/selective-ui.min.css.br +0 -0
  9. package/dist/selective-ui.min.js +2 -2
  10. package/dist/selective-ui.min.js.br +0 -0
  11. package/dist/selective-ui.umd.js +254 -555
  12. package/dist/selective-ui.umd.js.map +1 -1
  13. package/package.json +12 -12
  14. package/src/ts/adapter/mixed-adapter.ts +147 -68
  15. package/src/ts/components/accessorybox.ts +14 -11
  16. package/src/ts/components/directive.ts +1 -1
  17. package/src/ts/components/option-handle.ts +12 -9
  18. package/src/ts/components/placeholder.ts +5 -5
  19. package/src/ts/components/popup/empty-state.ts +5 -5
  20. package/src/ts/components/popup/loading-state.ts +5 -5
  21. package/src/ts/components/popup/popup.ts +138 -76
  22. package/src/ts/components/searchbox.ts +17 -13
  23. package/src/ts/components/selectbox.ts +242 -81
  24. package/src/ts/core/base/adapter.ts +39 -14
  25. package/src/ts/core/base/fenwick.ts +3 -2
  26. package/src/ts/core/base/lifecycle.ts +14 -4
  27. package/src/ts/core/base/model.ts +17 -15
  28. package/src/ts/core/base/recyclerview.ts +7 -5
  29. package/src/ts/core/base/view.ts +10 -5
  30. package/src/ts/core/base/virtual-recyclerview.ts +89 -37
  31. package/src/ts/core/model-manager.ts +48 -21
  32. package/src/ts/core/search-controller.ts +174 -56
  33. package/src/ts/global.ts +5 -8
  34. package/src/ts/index.ts +2 -2
  35. package/src/ts/models/group-model.ts +33 -8
  36. package/src/ts/models/option-model.ts +60 -19
  37. package/src/ts/services/dataset-observer.ts +6 -3
  38. package/src/ts/services/ea-observer.ts +1 -1
  39. package/src/ts/services/effector.ts +22 -12
  40. package/src/ts/services/refresher.ts +7 -3
  41. package/src/ts/services/resize-observer.ts +24 -11
  42. package/src/ts/services/select-observer.ts +2 -2
  43. package/src/ts/types/components/popup.type.ts +18 -1
  44. package/src/ts/types/components/searchbox.type.ts +43 -30
  45. package/src/ts/types/components/state.box.type.ts +1 -1
  46. package/src/ts/types/core/base/adapter.type.ts +13 -5
  47. package/src/ts/types/core/base/lifecycle.type.ts +1 -2
  48. package/src/ts/types/core/base/model.type.ts +3 -3
  49. package/src/ts/types/core/base/recyclerview.type.ts +7 -5
  50. package/src/ts/types/core/base/view.type.ts +6 -6
  51. package/src/ts/types/core/base/virtual-recyclerview.type.ts +45 -46
  52. package/src/ts/types/core/search-controller.type.ts +18 -2
  53. package/src/ts/types/css.d.ts +1 -0
  54. package/src/ts/types/plugins/plugin.type.ts +2 -2
  55. package/src/ts/types/services/effector.type.ts +25 -25
  56. package/src/ts/types/services/resize-observer.type.ts +23 -12
  57. package/src/ts/types/utils/callback-scheduler.type.ts +2 -2
  58. package/src/ts/types/utils/ievents.type.ts +1 -1
  59. package/src/ts/types/utils/istorage.type.ts +62 -60
  60. package/src/ts/types/utils/libs.type.ts +19 -17
  61. package/src/ts/types/utils/selective.type.ts +6 -3
  62. package/src/ts/types/views/view.group.type.ts +9 -5
  63. package/src/ts/types/views/view.option.type.ts +39 -17
  64. package/src/ts/utils/callback-scheduler.ts +12 -7
  65. package/src/ts/utils/ievents.ts +12 -5
  66. package/src/ts/utils/istorage.ts +5 -3
  67. package/src/ts/utils/libs.ts +122 -43
  68. package/src/ts/utils/selective.ts +15 -8
  69. package/src/ts/views/group-view.ts +11 -9
  70. package/src/ts/views/option-view.ts +37 -18
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "selective-ui",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "An overlay for the HTML select element.",
5
5
  "author": "Huỳnh Công Xuân Mai",
6
6
  "license": "MIT",
@@ -37,28 +37,28 @@
37
37
  "typecheck": "tsc -p tsconfig.json --noEmit"
38
38
  },
39
39
  "devDependencies": {
40
- "@rollup/plugin-commonjs": "^29.0.0",
40
+ "@rollup/plugin-commonjs": "^29.0.2",
41
41
  "@rollup/plugin-node-resolve": "^16.0.3",
42
42
  "@rollup/plugin-replace": "^6.0.3",
43
- "@rollup/plugin-terser": "^0.4.4",
43
+ "@rollup/plugin-terser": "^1.0.0",
44
44
  "@rollup/plugin-typescript": "^12.3.0",
45
45
  "@testing-library/dom": "^10.4.1",
46
46
  "@testing-library/jest-dom": "^6.9.1",
47
47
  "@types/jest": "^30.0.0",
48
- "autoprefixer": "^10.4.24",
49
- "cssnano": "^7.1.2",
50
- "jest": "^30.2.0",
51
- "jest-environment-jsdom": "^30.2.0",
48
+ "autoprefixer": "^10.4.27",
49
+ "cssnano": "^7.1.4",
50
+ "jest": "^30.3.0",
51
+ "jest-environment-jsdom": "^30.3.0",
52
52
  "mutation-observer": "^1.0.3",
53
- "postcss": "^8.5.6",
54
- "rimraf": "^6.1.2",
55
- "rollup": "^4.57.1",
53
+ "postcss": "^8.5.8",
54
+ "rimraf": "^6.1.3",
55
+ "rollup": "^4.60.1",
56
56
  "rollup-plugin-brotli": "^3.1.0",
57
57
  "rollup-plugin-postcss": "^4.0.2",
58
- "ts-jest": "^29.4.6",
58
+ "ts-jest": "^29.4.9",
59
59
  "ts-node": "^10.9.2",
60
60
  "tslib": "^2.8.1",
61
- "typescript": "^5.9.3"
61
+ "typescript": "^6.0.2"
62
62
  },
63
63
  "keywords": [
64
64
  "select",
@@ -3,9 +3,16 @@ import { GroupModel } from "../models/group-model";
3
3
  import { OptionModel } from "../models/option-model";
4
4
  import { GroupView } from "../views/group-view";
5
5
  import { OptionView } from "../views/option-view";
6
- import { MixedItem, VisibilityStats } from "../types/core/base/mixed-adapter.type";
6
+ import {
7
+ MixedItem,
8
+ VisibilityStats,
9
+ } from "../types/core/base/mixed-adapter.type";
7
10
  import { IEventCallback } from "../types/utils/ievents.type";
8
- import { ImagePosition, LabelHalign, LabelValign } from "../types/views/view.option.type";
11
+ import {
12
+ ImagePosition,
13
+ LabelHalign,
14
+ LabelValign,
15
+ } from "../types/views/view.option.type";
9
16
  import { Libs } from "../utils/libs";
10
17
  import { LifecycleState } from "../types/core/base/lifecycle.type";
11
18
  import { Lifecycle } from "../core/base/lifecycle";
@@ -72,25 +79,27 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
72
79
  *
73
80
  * @internal
74
81
  */
75
- public options: SelectiveOptions | null = null;
82
+ public options?: SelectiveOptions;
76
83
 
77
84
  /**
78
85
  * Subscribers for aggregated visibility statistics.
79
86
  * Fired via a debounced scheduler to avoid repeated recomputation during batch updates.
80
87
  */
81
- private visibilityChangedCallbacks: Array<(stats: VisibilityStats) => void> = [];
88
+ private visibilityChangedCallbacks: Array<
89
+ (stats: VisibilityStats) => void
90
+ > = [];
82
91
 
83
92
  /**
84
93
  * Flat index of the currently highlighted option.
85
94
  * `-1` indicates "no highlight".
86
95
  */
87
- private currentHighlightIndex = -1;
96
+ private currentHighlightIndex: number = -1;
88
97
 
89
98
  /**
90
99
  * Cached pointer to the selected option in single-select mode.
91
100
  * Used to efficiently clear previous selection when selecting a new option.
92
101
  */
93
- private selectedItemSingle: OptionModel | null = null;
102
+ private selectedItemSingle?: OptionModel;
94
103
 
95
104
  /** Top-level group models (if any). */
96
105
  public groups: GroupModel[] = [];
@@ -127,11 +136,13 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
127
136
  * @returns {void}
128
137
  * @override
129
138
  */
130
- public override init() {
139
+ public override init(): void {
131
140
  Libs.callbackScheduler.on(
132
141
  `sche_vis_${this.adapterKey}`,
133
142
  () => {
134
- const visibleCount = this.flatOptions.filter((item) => item.visible).length;
143
+ const visibleCount = this.flatOptions.filter(
144
+ (item) => item.visible,
145
+ ).length;
135
146
  const totalCount = this.flatOptions.length;
136
147
 
137
148
  this.visibilityChangedCallbacks.forEach((callback) => {
@@ -146,7 +157,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
146
157
  // Proxy hook; allows other listeners to chain after visibility aggregation.
147
158
  Libs.callbackScheduler.run(`sche_vis_proxy_${this.adapterKey}`);
148
159
  },
149
- { debounce: 10 }
160
+ { debounce: 10 },
150
161
  );
151
162
 
152
163
  super.init();
@@ -187,8 +198,12 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
187
198
  * @returns {GroupView | OptionView} A view instance matching the item type.
188
199
  * @override
189
200
  */
190
- override viewHolder(parent: HTMLElement, item: MixedItem): GroupView | OptionView {
191
- if (item instanceof GroupModel) return new GroupView(parent, this.options);
201
+ override viewHolder(
202
+ parent: HTMLElement,
203
+ item: MixedItem,
204
+ ): GroupView | OptionView {
205
+ if (item instanceof GroupModel)
206
+ return new GroupView(parent, this.options);
192
207
  return new OptionView(parent, this.options);
193
208
  }
194
209
 
@@ -200,12 +215,16 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
200
215
  * - Performs one-time listener binding guarded by `item.isInit`.
201
216
  *
202
217
  * @param {MixedItem} item - {@link GroupModel} or {@link OptionModel}.
203
- * @param {GroupView | OptionView | null} viewer - The view instance that will render the model.
218
+ * @param {GroupView | OptionView} viewer - The view instance that will render the model.
204
219
  * @param {number} position - Position in the top-level mixed list.
205
220
  * @returns {void}
206
221
  * @override
207
222
  */
208
- override onViewHolder(item: MixedItem, viewer: GroupView | OptionView | null, position: number): void {
223
+ override onViewHolder(
224
+ item: MixedItem,
225
+ viewer?: GroupView | OptionView,
226
+ position?: number,
227
+ ): void {
209
228
  item.position = position;
210
229
 
211
230
  if (item instanceof GroupModel) {
@@ -237,7 +256,11 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
237
256
  * @param {number} position - Group index in the top-level list.
238
257
  * @returns {void}
239
258
  */
240
- private handleGroupView(groupModel: GroupModel, groupView: GroupView, position: number): void {
259
+ private handleGroupView(
260
+ groupModel: GroupModel,
261
+ groupView: GroupView,
262
+ position: number,
263
+ ): void {
241
264
  super.onViewHolder(groupModel, groupView, position);
242
265
  groupModel.view = groupView;
243
266
 
@@ -251,16 +274,23 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
251
274
  groupModel.toggleCollapse();
252
275
  });
253
276
 
254
- groupModel.onCollapsedChanged((evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => {
255
- void evtToken;
256
-
257
- model.items.forEach((optItem) => {
258
- const optView = optItem.view?.getView?.();
259
- if (optView) optView.style.display = collapsed ? "none" : "";
260
- });
277
+ groupModel.onCollapsedChanged(
278
+ (
279
+ evtToken: IEventCallback,
280
+ model: GroupModel,
281
+ collapsed: boolean,
282
+ ) => {
283
+ void evtToken;
284
+
285
+ model.items.forEach((optItem) => {
286
+ const optView = optItem.view?.getView?.();
287
+ if (optView)
288
+ optView.style.display = collapsed ? "none" : "";
289
+ });
261
290
 
262
- this.onCollapsedChange(model, collapsed);
263
- });
291
+ this.onCollapsedChange(model, collapsed);
292
+ },
293
+ );
264
294
  }
265
295
 
266
296
  const itemsContainer = groupView.getItemsContainer();
@@ -307,7 +337,11 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
307
337
  * @param {number} position - Option index within its group list (or rendering context).
308
338
  * @returns {void}
309
339
  */
310
- private handleOptionView(optionModel: OptionModel, optionViewer: OptionView, position: number): void {
340
+ private handleOptionView(
341
+ optionModel: OptionModel,
342
+ optionViewer: OptionView,
343
+ position: number,
344
+ ): void {
311
345
  optionViewer.isMultiple = this.isMultiple;
312
346
  optionViewer.hasImage = optionModel.hasImage;
313
347
 
@@ -329,8 +363,10 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
329
363
  if (optionModel.hasImage) {
330
364
  const imageTag = optionViewer.view.tags.OptionImage;
331
365
  if (imageTag) {
332
- if (imageTag.src !== optionModel.imageSrc) imageTag.src = optionModel.imageSrc;
333
- if (imageTag.alt !== optionModel.text) imageTag.alt = optionModel.text;
366
+ if (imageTag.src !== optionModel.imageSrc)
367
+ imageTag.src = optionModel.imageSrc;
368
+ if (imageTag.alt !== optionModel.text)
369
+ imageTag.alt = optionModel.text;
334
370
  }
335
371
  }
336
372
 
@@ -338,45 +374,73 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
338
374
  optionViewer.view.tags.LabelContent.innerHTML = optionModel.text;
339
375
 
340
376
  if (!optionModel.isInit) {
341
- optionViewer.view.tags.OptionView.addEventListener("click", async (ev: MouseEvent) => {
342
- ev.stopPropagation();
343
- ev.preventDefault();
344
-
345
- if (this.isSkipEvent) return;
346
-
347
- if (this.isMultiple) {
348
- await this.changingProp("select");
349
- optionModel.selected = !optionModel.selected;
350
- } else if (optionModel.selected !== true) {
351
- await this.changingProp("select");
352
- if (this.selectedItemSingle) this.selectedItemSingle.selected = false;
353
- optionModel.selected = true;
354
- }
355
- });
377
+ optionViewer.view.tags.OptionView.addEventListener(
378
+ "click",
379
+ async (ev: MouseEvent) => {
380
+ ev.stopPropagation();
381
+ ev.preventDefault();
382
+
383
+ if (this.isSkipEvent) return;
384
+
385
+ if (this.isMultiple) {
386
+ await this.changingProp("select");
387
+ optionModel.selected = !optionModel.selected;
388
+ } else if (optionModel.selected !== true) {
389
+ await this.changingProp("select");
390
+ if (this.selectedItemSingle)
391
+ this.selectedItemSingle.selected = false;
392
+ optionModel.selected = true;
393
+ }
394
+ },
395
+ );
356
396
 
357
397
  optionViewer.view.tags.OptionView.title = optionModel.textContent;
358
398
 
359
- optionViewer.view.tags.OptionView.addEventListener("mouseenter", () => {
360
- if (this.isSkipEvent) return;
361
- this.setHighlight(this.flatOptions.indexOf(optionModel), false);
362
- });
399
+ optionViewer.view.tags.OptionView.addEventListener(
400
+ "mouseenter",
401
+ () => {
402
+ if (this.isSkipEvent) return;
403
+ this.setHighlight(
404
+ this.flatOptions.indexOf(optionModel),
405
+ false,
406
+ );
407
+ },
408
+ );
363
409
 
364
410
  // External selection notification (user-facing semantics).
365
- optionModel.onSelected((_evtToken: IEventCallback, _el: OptionModel, _selected: boolean) => {
366
- this.changeProp("selected");
367
- });
411
+ optionModel.onSelected(
412
+ (
413
+ _evtToken: IEventCallback,
414
+ _el: OptionModel,
415
+ _selected: boolean,
416
+ ) => {
417
+ this.changeProp("selected");
418
+ },
419
+ );
368
420
 
369
421
  // Internal selection notification (non-trigger semantics).
370
- optionModel.onInternalSelected((_evtToken: IEventCallback, _el: OptionModel, selected: boolean) => {
371
- if (selected) this.selectedItemSingle = optionModel;
372
- this.changeProp("selected_internal");
373
- });
422
+ optionModel.onInternalSelected(
423
+ (
424
+ _evtToken: IEventCallback,
425
+ _el: OptionModel,
426
+ selected: boolean,
427
+ ) => {
428
+ if (selected) this.selectedItemSingle = optionModel;
429
+ this.changeProp("selected_internal");
430
+ },
431
+ );
374
432
 
375
433
  // Visibility changes affect group visibility and aggregated visibility stats.
376
- optionModel.onVisibilityChanged((_evtToken: IEventCallback, model: OptionModel, _visible: boolean) => {
377
- model.group?.updateVisibility();
378
- this.notifyVisibilityChanged();
379
- });
434
+ optionModel.onVisibilityChanged(
435
+ (
436
+ _evtToken: IEventCallback,
437
+ model: OptionModel,
438
+ _visible: boolean,
439
+ ) => {
440
+ model.group?.updateVisibility();
441
+ this.notifyVisibilityChanged();
442
+ },
443
+ );
380
444
  }
381
445
 
382
446
  // Ensure single-select cache and suppress re-trigger when model is already selected.
@@ -453,7 +517,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
453
517
 
454
518
  Libs.callbackScheduler.clear(`sche_vis_${this.adapterKey}`);
455
519
 
456
- this.groups.forEach(group => {
520
+ this.groups.forEach((group) => {
457
521
  group.destroy();
458
522
  });
459
523
 
@@ -506,7 +570,9 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
506
570
  * @param {(stats: VisibilityStats) => void} callback - Invoked with `{ visibleCount, totalCount, hasVisible, isEmpty }`.
507
571
  * @returns {void}
508
572
  */
509
- public onVisibilityChanged(callback: (stats: VisibilityStats) => void): void {
573
+ public onVisibilityChanged(
574
+ callback: (stats: VisibilityStats) => void,
575
+ ): void {
510
576
  this.visibilityChangedCallbacks.push(callback);
511
577
  }
512
578
 
@@ -525,7 +591,9 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
525
591
  * @returns {VisibilityStats} Aggregated stats: `{ visibleCount, totalCount, hasVisible, isEmpty }`.
526
592
  */
527
593
  public getVisibilityStats(): VisibilityStats {
528
- const visibleCount = this.flatOptions.filter((item) => item.visible).length;
594
+ const visibleCount = this.flatOptions.filter(
595
+ (item) => item.visible,
596
+ ).length;
529
597
  const totalCount = this.flatOptions.length;
530
598
 
531
599
  return {
@@ -560,7 +628,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
560
628
  if (visibleOptions.length === 0) return;
561
629
 
562
630
  let currentVisibleIndex = visibleOptions.findIndex(
563
- (opt) => opt === this.flatOptions[this.currentHighlightIndex]
631
+ (opt) => opt === this.flatOptions[this.currentHighlightIndex],
564
632
  );
565
633
  if (currentVisibleIndex === -1) currentVisibleIndex = -1;
566
634
 
@@ -589,7 +657,10 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
589
657
  * @returns {void}
590
658
  */
591
659
  public selectHighlighted(): void {
592
- if (this.currentHighlightIndex > -1 && this.flatOptions[this.currentHighlightIndex]) {
660
+ if (
661
+ this.currentHighlightIndex > -1 &&
662
+ this.flatOptions[this.currentHighlightIndex]
663
+ ) {
593
664
  const item = this.flatOptions[this.currentHighlightIndex];
594
665
  if (item.visible) {
595
666
  const viewEl = item.view?.getView?.();
@@ -614,7 +685,10 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
614
685
  * @param {boolean} [isScrollToView=true] - Whether to scroll the highlighted item into view.
615
686
  * @returns {void}
616
687
  */
617
- public setHighlight(target: number | OptionModel, isScrollToView: boolean = true): void {
688
+ public setHighlight(
689
+ target: number | OptionModel,
690
+ isScrollToView: boolean = true,
691
+ ): void {
618
692
  let index = 0;
619
693
 
620
694
  if (typeof target === "number") {
@@ -626,7 +700,10 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
626
700
  index = 0;
627
701
  }
628
702
 
629
- if (this.currentHighlightIndex > -1 && this.flatOptions[this.currentHighlightIndex]) {
703
+ if (
704
+ this.currentHighlightIndex > -1 &&
705
+ this.flatOptions[this.currentHighlightIndex]
706
+ ) {
630
707
  this.flatOptions[this.currentHighlightIndex].highlighted = false;
631
708
  }
632
709
 
@@ -640,10 +717,12 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
640
717
  if (isScrollToView) {
641
718
  const el = item.view?.getView?.();
642
719
  if (el) {
643
- el.scrollIntoView({ block: 'center', behavior: 'smooth' });
720
+ el.scrollIntoView({ block: "center", behavior: "smooth" });
644
721
  } else {
645
722
  // If virtualized, ensure the item is rendered before trying to scroll.
646
- this.recyclerView?.ensureRendered?.(i, { scrollIntoView: true });
723
+ this.recyclerView?.ensureRendered?.(i, {
724
+ scrollIntoView: true,
725
+ });
647
726
  }
648
727
  }
649
728
 
@@ -664,7 +743,7 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
664
743
  * @param {string} [id] - Optional DOM id of the highlighted view element (when available).
665
744
  * @returns {void}
666
745
  */
667
- public onHighlightChange(index: number, id?: string): void { }
746
+ public onHighlightChange(index: number, id?: string): void {}
668
747
 
669
748
  /**
670
749
  * Hook called whenever a group's collapsed state changes.
@@ -678,5 +757,5 @@ export class MixedAdapter extends Adapter<MixedItem, GroupView | OptionView> {
678
757
  * @param {boolean} collapsed - New collapsed state.
679
758
  * @returns {void}
680
759
  */
681
- public onCollapsedChange(model: GroupModel, collapsed: boolean): void { }
682
- }
760
+ public onCollapsedChange(model: GroupModel, collapsed: boolean): void {}
761
+ }
@@ -61,13 +61,13 @@ export class AccessoryBox extends Lifecycle {
61
61
  * Mounted structure returned by the node mounting helper.
62
62
  * Contains the root element (`view`) and any tag handles (if present).
63
63
  */
64
- private nodeMounted: MountViewResult | null = null;
64
+ private nodeMounted?: MountViewResult;
65
65
 
66
66
  /**
67
67
  * Root DOM element of the accessory box (hidden by default).
68
68
  * Created during {@link init} and removed during {@link destroy}.
69
69
  */
70
- private node: HTMLDivElement | null = null;
70
+ private node?: HTMLDivElement;
71
71
 
72
72
  /**
73
73
  * Component configuration (texts, behavior, placement).
@@ -77,25 +77,25 @@ export class AccessoryBox extends Lifecycle {
77
77
  * - `multiple` (multi-select mode)
78
78
  * - `textAccessoryDeselect` (a11y label prefix)
79
79
  */
80
- private options: SelectiveOptions | null = null;
80
+ private options?: SelectiveOptions;
81
81
 
82
82
  /**
83
83
  * The Select UI mask element used as the positioning reference.
84
84
  * Provided by {@link setRoot}.
85
85
  */
86
- private selectUIMask: HTMLDivElement | null = null;
86
+ private selectUIMask?: HTMLDivElement;
87
87
 
88
88
  /**
89
89
  * Parent container that hosts both the Select UI mask and the accessory box.
90
90
  * Computed from `selectUIMask.parentElement`.
91
91
  */
92
- private parentMask: HTMLDivElement | null = null;
92
+ private parentMask?: HTMLDivElement;
93
93
 
94
94
  /**
95
95
  * ModelManager used to run selection pipelines and coordinate state updates.
96
96
  * This component does not own selection state; it delegates to the model layer.
97
97
  */
98
- private modelManager: ModelManager<MixedItem, MixedAdapter> | null = null;
98
+ private modelManager?: ModelManager<MixedItem, MixedAdapter>;
99
99
 
100
100
  /**
101
101
  * Current selected option models rendered as chips.
@@ -106,9 +106,9 @@ export class AccessoryBox extends Lifecycle {
106
106
  /**
107
107
  * Creates an AccessoryBox and optionally initializes it with configuration.
108
108
  *
109
- * @param {SelectiveOptions | null} [options=null] - Configuration controlling placement/visibility and texts.
109
+ * @param {SelectiveOptions} [options=null] - Configuration controlling placement/visibility and texts.
110
110
  */
111
- public constructor(options: SelectiveOptions | null = null) {
111
+ public constructor(options?: SelectiveOptions) {
112
112
  super();
113
113
  if (options) this.initialize(options);
114
114
  }
@@ -211,7 +211,8 @@ export class AccessoryBox extends Lifecycle {
211
211
  !this.node ||
212
212
  !this.selectUIMask ||
213
213
  !this.options
214
- ) return;
214
+ )
215
+ return;
215
216
 
216
217
  const ref =
217
218
  this.options.accessoryStyle === "top"
@@ -281,7 +282,9 @@ export class AccessoryBox extends Lifecycle {
281
282
  title: `${this.options!.textAccessoryDeselect}${modelData.textContent}`,
282
283
  onclick: async (evt: MouseEvent) => {
283
284
  evt.preventDefault();
284
- await this.modelManager?.triggerChanging?.("select");
285
+ await this.modelManager?.triggerChanging?.(
286
+ "select",
287
+ );
285
288
  modelData.selected = false;
286
289
  },
287
290
  },
@@ -392,4 +395,4 @@ export class AccessoryBox extends Lifecycle {
392
395
 
393
396
  super.destroy();
394
397
  }
395
- }
398
+ }
@@ -113,4 +113,4 @@ export class Directive extends Lifecycle {
113
113
 
114
114
  super.destroy();
115
115
  }
116
- }
116
+ }
@@ -49,7 +49,7 @@ export class OptionHandle extends Lifecycle {
49
49
  *
50
50
  * @internal
51
51
  */
52
- private nodeMounted: MountViewResult | null = null;
52
+ private nodeMounted?: MountViewResult;
53
53
 
54
54
  /**
55
55
  * Root element of this control.
@@ -57,7 +57,7 @@ export class OptionHandle extends Lifecycle {
57
57
  * Created during {@link initialize}. This node is used by {@link show}/{@link hide}
58
58
  * and removed during {@link destroy}.
59
59
  */
60
- public node: HTMLDivElement | null = null;
60
+ public node?: HTMLDivElement;
61
61
 
62
62
  /**
63
63
  * Configuration snapshot used for:
@@ -68,7 +68,7 @@ export class OptionHandle extends Lifecycle {
68
68
  *
69
69
  * @internal
70
70
  */
71
- private options: SelectiveOptions | null = null;
71
+ private options?: SelectiveOptions;
72
72
 
73
73
  /**
74
74
  * Callback list invoked when the "Select all" control is activated.
@@ -99,7 +99,7 @@ export class OptionHandle extends Lifecycle {
99
99
  *
100
100
  * @param options - Feature flags and labels for the two actions.
101
101
  */
102
- public constructor(options: SelectiveOptions | null = null) {
102
+ public constructor(options?: SelectiveOptions) {
103
103
  super();
104
104
  if (options) this.initialize(options);
105
105
  }
@@ -172,7 +172,10 @@ export class OptionHandle extends Lifecycle {
172
172
  */
173
173
  private available(): boolean {
174
174
  if (!this.options) return false;
175
- return Libs.string2Boolean(this.options.multiple) && Libs.string2Boolean(this.options.selectall);
175
+ return (
176
+ Libs.string2Boolean(this.options.multiple) &&
177
+ Libs.string2Boolean(this.options.selectall)
178
+ );
176
179
  }
177
180
 
178
181
  /**
@@ -231,7 +234,7 @@ export class OptionHandle extends Lifecycle {
231
234
  *
232
235
  * @param action - Callback invoked on activation; ignored when not a function.
233
236
  */
234
- public onSelectAll(action: ((...args: unknown[]) => unknown) | null = null): void {
237
+ public onSelectAll(action?: (...args: unknown[]) => unknown): void {
235
238
  if (typeof action === "function") {
236
239
  this.actionOnSelectAll.push(action);
237
240
  }
@@ -248,7 +251,7 @@ export class OptionHandle extends Lifecycle {
248
251
  *
249
252
  * @param action - Callback invoked on activation; ignored when not a function.
250
253
  */
251
- public onDeSelectAll(action: ((...args: unknown[]) => unknown) | null = null): void {
254
+ public onDeSelectAll(action?: (...args: unknown[]) => unknown): void {
252
255
  if (typeof action === "function") {
253
256
  this.actionOnDeSelectAll.push(action);
254
257
  }
@@ -277,8 +280,8 @@ export class OptionHandle extends Lifecycle {
277
280
  this.options = null;
278
281
  this.actionOnSelectAll = null;
279
282
  this.actionOnDeSelectAll = null;
280
- this.node = null
283
+ this.node = null;
281
284
 
282
285
  super.destroy();
283
286
  }
284
- }
287
+ }
@@ -38,7 +38,7 @@ export class PlaceHolder extends Lifecycle {
38
38
  * Created during {@link initialize}. Removed from the DOM during {@link destroy}.
39
39
  * `null` before initialization and after destruction.
40
40
  */
41
- public node: HTMLElement | null = null;
41
+ public node?: HTMLElement;
42
42
 
43
43
  /**
44
44
  * Configuration snapshot used to render and optionally persist placeholder content.
@@ -51,7 +51,7 @@ export class PlaceHolder extends Lifecycle {
51
51
  *
52
52
  * @internal
53
53
  */
54
- private options: SelectiveOptions | null = null;
54
+ private options?: SelectiveOptions;
55
55
 
56
56
  /**
57
57
  * Creates a new {@link PlaceHolder}.
@@ -62,7 +62,7 @@ export class PlaceHolder extends Lifecycle {
62
62
  *
63
63
  * @param options - Select UI options containing placeholder content and rendering flags.
64
64
  */
65
- constructor(options: SelectiveOptions | null) {
65
+ constructor(options?: SelectiveOptions) {
66
66
  super();
67
67
  if (options) this.initialize(options);
68
68
  }
@@ -144,11 +144,11 @@ export class PlaceHolder extends Lifecycle {
144
144
  if (this.is(LifecycleState.DESTROYED)) {
145
145
  return;
146
146
  }
147
-
147
+
148
148
  this.node?.remove();
149
149
  this.node = null;
150
150
  this.options = null;
151
151
 
152
152
  super.destroy();
153
153
  }
154
- }
154
+ }