selective-ui 1.2.2 → 1.2.4

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 (53) hide show
  1. package/dist/selective-ui.css.map +1 -1
  2. package/dist/selective-ui.esm.js +2178 -695
  3. package/dist/selective-ui.esm.js.map +1 -1
  4. package/dist/selective-ui.esm.min.js +2 -2
  5. package/dist/selective-ui.esm.min.js.br +0 -0
  6. package/dist/selective-ui.min.js +2 -2
  7. package/dist/selective-ui.min.js.br +0 -0
  8. package/dist/selective-ui.umd.js +2179 -696
  9. package/dist/selective-ui.umd.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/ts/adapter/mixed-adapter.ts +167 -80
  12. package/src/ts/components/accessorybox.ts +155 -34
  13. package/src/ts/components/directive.ts +62 -11
  14. package/src/ts/components/option-handle.ts +124 -28
  15. package/src/ts/components/placeholder.ts +73 -16
  16. package/src/ts/components/popup/empty-state.ts +126 -0
  17. package/src/ts/components/popup/loading-state.ts +120 -0
  18. package/src/ts/components/{popup.ts → popup/popup.ts} +169 -72
  19. package/src/ts/components/searchbox.ts +82 -16
  20. package/src/ts/components/selectbox.ts +207 -115
  21. package/src/ts/core/base/adapter.ts +121 -55
  22. package/src/ts/core/base/lifecycle.ts +175 -0
  23. package/src/ts/core/base/model.ts +63 -32
  24. package/src/ts/core/base/recyclerview.ts +56 -18
  25. package/src/ts/core/base/view.ts +56 -19
  26. package/src/ts/core/base/virtual-recyclerview.ts +322 -128
  27. package/src/ts/core/model-manager.ts +18 -11
  28. package/src/ts/core/search-controller.ts +148 -25
  29. package/src/ts/global.ts +5 -5
  30. package/src/ts/index.ts +5 -5
  31. package/src/ts/models/group-model.ts +27 -6
  32. package/src/ts/models/option-model.ts +29 -6
  33. package/src/ts/services/ea-observer.ts +6 -6
  34. package/src/ts/services/effector.ts +2 -2
  35. package/src/ts/services/select-observer.ts +1 -8
  36. package/src/ts/types/components/searchbox.type.ts +1 -1
  37. package/src/ts/types/core/base/adapter.type.ts +2 -1
  38. package/src/ts/types/core/base/lifecycle.type.ts +62 -0
  39. package/src/ts/types/core/base/model.type.ts +3 -1
  40. package/src/ts/types/core/base/recyclerview.type.ts +2 -8
  41. package/src/ts/types/core/base/view.type.ts +36 -24
  42. package/src/ts/utils/callback-scheduler.ts +38 -18
  43. package/src/ts/utils/istorage.ts +1 -1
  44. package/src/ts/utils/selective.ts +153 -36
  45. package/src/ts/views/group-view.ts +59 -21
  46. package/src/ts/views/option-view.ts +137 -68
  47. package/src/ts/components/empty-state.ts +0 -68
  48. package/src/ts/components/loading-state.ts +0 -66
  49. /package/src/css/components/{empty-state.css → popup/empty-state.css} +0 -0
  50. /package/src/css/components/{loading-state.css → popup/loading-state.css} +0 -0
  51. /package/src/css/components/{popup.css → popup/popup.css} +0 -0
  52. /package/src/css/{components/optgroup.css → views/group-view.css} +0 -0
  53. /package/src/css/{components/option.css → views/option-view.css} +0 -0
@@ -16,7 +16,7 @@ export class SelectObserver {
16
16
 
17
17
  /**
18
18
  * Initializes the SelectObserver for a given <select> element.
19
- * Captures the initial snapshot, sets up a MutationObserver, and listens for custom "options:changed" events.
19
+ * Captures the initial snapshot, sets up a MutationObserver.
20
20
  * Changes are debounced to prevent excessive calls.
21
21
  *
22
22
  * @param {HTMLSelectElement} select - The <select> element to observe.
@@ -31,13 +31,6 @@ export class SelectObserver {
31
31
  this.handleChange();
32
32
  }, this._DEBOUNCE_DELAY);
33
33
  });
34
-
35
- select.addEventListener("options:changed", () => {
36
- if (this.debounceTimer) clearTimeout(this.debounceTimer);
37
- this.debounceTimer = setTimeout(() => {
38
- this.handleChange();
39
- }, this._DEBOUNCE_DELAY);
40
- });
41
34
  }
42
35
 
43
36
  /**
@@ -2,7 +2,7 @@ import { PlaceHolder } from "src/ts/components/placeholder";
2
2
  import { MountViewResult } from "../utils/libs.type";
3
3
  import { Directive } from "src/ts/components/directive";
4
4
  import { SearchBox } from "src/ts/components/searchbox";
5
- import { Popup } from "src/ts/components/popup";
5
+ import { Popup } from "src/ts/components/popup/popup";
6
6
  import { EffectorInterface } from "../services/effector.type";
7
7
  import { AccessoryBox } from "src/ts/components/accessorybox";
8
8
  import { SearchController } from "src/ts/core/search-controller";
@@ -1,3 +1,4 @@
1
+ import { Lifecycle } from "src/ts/core/base/lifecycle";
1
2
  import type { ModelContract } from "./model.type";
2
3
 
3
4
  /**
@@ -5,7 +6,7 @@ import type { ModelContract } from "./model.type";
5
6
  *
6
7
  * @template TItem - A model type that implements ModelContract.
7
8
  */
8
- export interface AdapterContract<TItem extends ModelContract<any, any>> {
9
+ export interface AdapterContract<TItem extends ModelContract<any, any>> extends Lifecycle {
9
10
  /**
10
11
  * List of items managed by the adapter.
11
12
  * These items are rendered or updated in the associated container.
@@ -0,0 +1,62 @@
1
+
2
+ /**
3
+ * Enumerates the finite lifecycle states used across core classes (e.g., View/Model).
4
+ *
5
+ * State flow (happy path):
6
+ * NEW → INITIALIZED → MOUNTED → UPDATED → DESTROYED
7
+ *
8
+ * Notes:
9
+ * - `UPDATED` may be emitted multiple times after mount, depending on implementation.
10
+ * - `DESTROYED` is terminal; no further transitions should occur.
11
+ */
12
+ export enum LifecycleState {
13
+ /** The instance has been created but not initialized. */
14
+ NEW = "new",
15
+
16
+ /** Initialization logic has completed; ready to be mounted. */
17
+ INITIALIZED = "initialized",
18
+
19
+ /** The instance is mounted/attached to its environment (e.g., DOM). */
20
+ MOUNTED = "mounted",
21
+
22
+ /**
23
+ * The instance has been updated at least once after being mounted.
24
+ * Further updates typically keep the state at UPDATED.
25
+ */
26
+ UPDATED = "updated",
27
+
28
+ /** Teardown has run; resources/hooks have been released. */
29
+ DESTROYED = "destroyed",
30
+ }
31
+
32
+ /**
33
+ * Optional callbacks for lifecycle events.
34
+ *
35
+ * Implementers can subscribe to these hooks either by:
36
+ * - passing these functions to a contract that consumes `LifecycleHooks`, or
37
+ * - using the `Lifecycle.on(hook, fn)` API at runtime.
38
+ *
39
+ * Each callback is executed without arguments and should be side-effect free
40
+ * except for logic relevant to the lifecycle step.
41
+ */
42
+ export interface LifecycleHooks {
43
+ /** Invoked when transitioning from `NEW` → `INITIALIZED`. */
44
+ onInit?(): void;
45
+
46
+ /** Invoked when transitioning from `INITIALIZED` → `MOUNTED`. */
47
+ onMount?(): void;
48
+
49
+ /**
50
+ * Invoked when transitioning to `UPDATED`.
51
+ * May be emitted multiple times after the instance is mounted.
52
+ */
53
+ onUpdate?(): void;
54
+
55
+ /** Invoked when transitioning to `DESTROYED`. Should be idempotent. */
56
+ onDestroy?(): void;
57
+ }
58
+
59
+ export type LifecycleHookContext = {
60
+ state: LifecycleState;
61
+ prevState: LifecycleState;
62
+ };
@@ -1,10 +1,12 @@
1
+ import { Lifecycle } from "src/ts/core/base/lifecycle";
2
+
1
3
  /**
2
4
  * Generic model contract for binding a target element and an optional view.
3
5
  *
4
6
  * @template TTarget - The type of the target element (e.g., DOM element, data object).
5
7
  * @template TView - The type of the view associated with the target (e.g., UI component).
6
8
  */
7
- export interface ModelContract<TTarget, TView> {
9
+ export interface ModelContract<TTarget, TView> extends Lifecycle {
8
10
  /**
9
11
  * The target element that this model is bound to.
10
12
  * Can be null if not yet initialized.
@@ -1,3 +1,4 @@
1
+ import { Lifecycle } from "src/ts/core/base/lifecycle";
1
2
  import { AdapterContract } from "./adapter.type";
2
3
 
3
4
  /**
@@ -5,7 +6,7 @@ import { AdapterContract } from "./adapter.type";
5
6
  *
6
7
  * @template TAdapter - A concrete adapter type that implements AdapterContract.
7
8
  */
8
- export interface RecyclerViewContract<TAdapter extends AdapterContract<any>> {
9
+ export interface RecyclerViewContract<TAdapter extends AdapterContract<any>> extends Lifecycle {
9
10
  /**
10
11
  * The container element that hosts all item views.
11
12
  * Can be null until `setView` is called.
@@ -18,13 +19,6 @@ export interface RecyclerViewContract<TAdapter extends AdapterContract<any>> {
18
19
  */
19
20
  adapter: TAdapter | null;
20
21
 
21
- /**
22
- * Set or replace the container element used for rendering.
23
- *
24
- * @param viewElement - The root container element for the recycler view.
25
- */
26
- setView(viewElement: HTMLElement): void;
27
-
28
22
  /**
29
23
  * Attach an adapter and refresh the view accordingly.
30
24
  * Implementations typically call `render()` or reconcile the DOM.
@@ -1,43 +1,55 @@
1
+
2
+ import { Lifecycle } from "src/ts/core/base/lifecycle";
1
3
  import { MountViewResult } from "../../utils/libs.type";
2
4
 
3
5
  /**
4
- * Contract for a UI view created via `mountView`/`mountNode`.
5
- * Encapsulates the mounted view, its parent, and helper accessors.
6
+ * Contract definition for a UI View created via `mountView` or `mountNode`.
7
+ *
8
+ * A View encapsulates:
9
+ * - The mounted DOM structure
10
+ * - The root element
11
+ * - A strongly-typed map of named child elements
12
+ * - Lifecycle management hooks
6
13
  *
7
14
  * @template TTags - A map of tag names to their corresponding HTMLElement instances.
8
- * Example: `{ Root: HTMLDivElement, Button: HTMLButtonElement }`
15
+ * Example:
16
+ * ```ts
17
+ * {
18
+ * Root: HTMLDivElement;
19
+ * Button: HTMLButtonElement;
20
+ * }
21
+ * ```
9
22
  */
10
- export interface ViewContract<TTags extends Record<string, HTMLElement>> {
23
+ export interface ViewContract<TTags extends Record<string, HTMLElement>> extends Lifecycle {
24
+
11
25
  /**
12
- * The parent container into which the view is mounted.
13
- * Can be null before the view is attached.
26
+ * The parent DOM element into which the view is mounted.
27
+ *
28
+ * This value is:
29
+ * - `null` before the view is mounted
30
+ * - Set once the view is attached to the DOM
14
31
  */
15
32
  parent: HTMLElement | null;
16
33
 
17
34
  /**
18
- * The result returned by mountView/mountNode, including the root element
19
- * and the tag map used to retrieve specific nodes.
20
- * Can be null if the view has not been mounted yet.
35
+ * Internal representation of the mounted view returned by `mountView` or `mountNode`.
36
+ *
37
+ * Contains:
38
+ * - The root element of the view
39
+ * - A strongly-typed tag map for querying child elements
40
+ *
41
+ * Will be `null` if the view has not been mounted yet.
21
42
  */
22
43
  view: MountViewResult<TTags> | null;
23
44
 
24
45
  /**
25
- * Render or re-render the view.
26
- * Implementations typically (re)build DOM, bind events, and update state.
27
- */
28
- render(): void;
29
-
30
- /**
31
- * Update the view.
32
- * Implementations typically refresh displayed data without a full re-render.
33
- */
34
- update(): void;
35
-
36
- /**
37
- * Get the root HTMLElement for the mounted view.
46
+ * Returns the root HTMLElement of the mounted view.
47
+ *
48
+ * This is typically the top-level container element
49
+ * created by `mountView` / `mountNode`.
38
50
  *
39
- * @returns The root element produced by mountView/mountNode.
40
- * @throws If the view is not initialized.
51
+ * @returns The root HTMLElement of the view.
52
+ * @throws {Error} If the view has not been mounted or initialized.
41
53
  */
42
54
  getView(): HTMLElement;
43
55
  }
@@ -84,12 +84,18 @@ export class CallbackScheduler {
84
84
  * - If an entry has `once = true`, it is removed after execution by setting its slot to `undefined`.
85
85
  * (The list is not spliced to preserve indices.)
86
86
  */
87
- public run(key: TimerKey, ...params: any[]): void {
87
+ public run(key: TimerKey, ...params: any[]): Promise<void> | void {
88
88
  const executes = this.executeStored.get(key);
89
- if (!executes) return;
89
+ if (!executes || executes.length === 0) {
90
+ return Promise.resolve();
91
+ }
92
+
93
+ if (!this.timerRunner.has(key)) {
94
+ this.timerRunner.set(key, new Map());
95
+ }
90
96
 
91
- if (!this.timerRunner.has(key)) this.timerRunner.set(key, new Map());
92
97
  const runner = this.timerRunner.get(key)!;
98
+ const tasks: Promise<void>[] = [];
93
99
 
94
100
  for (let i = 0; i < executes.length; i++) {
95
101
  const entry = executes[i];
@@ -98,22 +104,36 @@ export class CallbackScheduler {
98
104
  const prev = runner.get(i);
99
105
  if (prev) clearTimeout(prev);
100
106
 
101
- const timer = setTimeout(() => {
102
- entry.callback(params.length > 0 ? params : null);
103
-
104
- if (entry.once) {
105
- // Preserve index stability by leaving an empty slot.
106
- executes[i] = undefined;
107
-
108
- // Cleanup the timer handle for this index.
109
- const current = runner.get(i);
110
- if (current) clearTimeout(current);
111
- runner.delete(i);
112
- }
113
- }, entry.timeout);
114
-
115
- runner.set(i, timer);
107
+ const task = new Promise<void>((resolve) => {
108
+ const timer = setTimeout(async () => {
109
+ try {
110
+ const resp = entry.callback(
111
+ params.length > 0 ? params : null
112
+ ) as any;
113
+
114
+ if (resp instanceof Promise) {
115
+ await resp;
116
+ }
117
+ } catch {} finally {
118
+ if (entry.once) {
119
+ executes[i] = undefined;
120
+
121
+ const current = runner.get(i);
122
+ if (current) clearTimeout(current);
123
+ runner.delete(i);
124
+ }
125
+
126
+ resolve();
127
+ }
128
+ }, entry.timeout);
129
+
130
+ runner.set(i, timer);
131
+ });
132
+
133
+ tasks.push(task);
116
134
  }
135
+
136
+ return Promise.all(tasks).then(() => void 0);
117
137
  }
118
138
 
119
139
  /**
@@ -38,7 +38,7 @@ export class iStorage {
38
38
  textAccessoryDeselect: "Deselect: ",
39
39
  animationtime: 200, // millisecond
40
40
  delaysearchtime: 200, // millisecond
41
- allowHtml: true,
41
+ allowHtml: false,
42
42
  maxSelected: 0,
43
43
  labelHalign: "left",
44
44
  labelValign: "center",
@@ -4,13 +4,31 @@ import { SelectBox } from "../components/selectbox";
4
4
  import { ElementAdditionObserver } from "../services/ea-observer";
5
5
  import { SelectiveActionApi, SelectiveOptions } from "../types/utils/selective.type";
6
6
  import { BinderMap, PropertiesType } from "../types/utils/istorage.type";
7
- import { Popup } from "../components/popup";
7
+ import { Lifecycle } from "../core/base/lifecycle";
8
+ import { LifecycleState } from "../types/core/base/lifecycle.type";
8
9
 
9
- export class Selective {
10
+ export class Selective extends Lifecycle {
10
11
  private EAObserver: ElementAdditionObserver;
11
12
 
12
13
  private bindedQueries: Map<string, SelectiveOptions> = new Map();
13
14
 
15
+ constructor() {
16
+ super();
17
+ this.init();
18
+ }
19
+
20
+ /**
21
+ * Override lifecycle init - Initialize Selective system
22
+ */
23
+ public init(): void {
24
+ if (!this.is(LifecycleState.NEW)) return;
25
+
26
+ // Initialize core properties
27
+ this.bindedQueries = new Map();
28
+
29
+ super.init();
30
+ }
31
+
14
32
  /**
15
33
  * Binds Selective UI to all <select> elements matching the query.
16
34
  * Merges provided options with defaults, schedules `on.load` callbacks,
@@ -20,27 +38,40 @@ export class Selective {
20
38
  * @param {object} options - Configuration overrides merged with defaults.
21
39
  */
22
40
  public bind(query: string, options: SelectiveOptions): void {
23
- const merged = Libs.mergeConfig(Libs.getDefaultConfig(), options) as SelectiveOptions;
41
+ // Auto-init if not initialized
42
+ if (this.is(LifecycleState.NEW)) {
43
+ this.init();
44
+ }
45
+
46
+ const merged = Libs.mergeConfig(
47
+ Libs.getDefaultConfig(),
48
+ options,
49
+ ) as SelectiveOptions;
24
50
 
25
51
  // Ensure hooks exist
26
52
  merged.on = merged.on ?? {};
27
- merged.on.load = (merged.on.load ?? []) as Array<(...args: any[]) => void>;
53
+ merged.on.load = (merged.on.load ?? []) as Array<
54
+ (...args: any[]) => void
55
+ >;
28
56
 
29
57
  this.bindedQueries.set(query, merged);
30
58
 
31
59
  const doneToken = Libs.randomString();
32
60
  Libs.callbackScheduler.on(doneToken, () => {
33
- iEvents.callEvent([this.find(query)], ...(merged.on!.load));
61
+ iEvents.callEvent([this.find(query)], ...merged.on!.load);
34
62
  Libs.callbackScheduler.clear(doneToken);
35
63
  merged.on!.load = [];
36
64
  });
37
65
 
38
66
  const selectElements = Libs.getElements(query) as HTMLSelectElement[];
67
+ let hasAnyBound = false;
68
+
39
69
  selectElements.forEach((item) => {
40
70
  (async () => {
41
71
  if (item.tagName === "SELECT") {
42
72
  Libs.removeUnbinderMap(item);
43
73
  if (this.applySelectBox(item, merged)) {
74
+ hasAnyBound = true;
44
75
  Libs.callbackScheduler.run(doneToken);
45
76
  }
46
77
  }
@@ -50,6 +81,34 @@ export class Selective {
50
81
  if (!Libs.getBindedCommand().includes(query)) {
51
82
  Libs.getBindedCommand().push(query);
52
83
  }
84
+
85
+ // Mount if first bind and has elements
86
+ if (this.is(LifecycleState.INITIALIZED) && hasAnyBound) {
87
+ this.mount();
88
+ }
89
+
90
+ // Trigger update if already mounted
91
+ if (this.is(LifecycleState.MOUNTED)) {
92
+ this.update();
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Override lifecycle mount - System is ready and has bound elements
98
+ */
99
+ public mount(): void {
100
+ if (this.state !== LifecycleState.INITIALIZED) return;
101
+
102
+ super.mount();
103
+ }
104
+
105
+ /**
106
+ * Override lifecycle update - Called when bindings change
107
+ */
108
+ public update(): void {
109
+ if (this.state !== LifecycleState.MOUNTED) return;
110
+
111
+ super.update();
53
112
  }
54
113
 
55
114
  /**
@@ -101,19 +160,27 @@ export class Selective {
101
160
  * Selective bindings automatically when they match previously bound queries.
102
161
  */
103
162
  public Observer(): void {
104
- this.EAObserver = new ElementAdditionObserver();
105
- this.EAObserver.onDetect((selectElement: HTMLSelectElement) => {
106
- this.bindedQueries.forEach((options, query) => {
107
- try {
108
- if (selectElement.matches(query)) {
109
- this.applySelectBox(selectElement, options);
163
+ if (!this.EAObserver) {
164
+ this.EAObserver = new ElementAdditionObserver();
165
+ this.EAObserver.onDetect((selectElement: HTMLSelectElement) => {
166
+ this.bindedQueries.forEach((options, query) => {
167
+ try {
168
+ if (selectElement.matches(query)) {
169
+ this.applySelectBox(selectElement, options);
170
+
171
+ // Trigger update when new element is bound
172
+ if (this.is(LifecycleState.MOUNTED)) {
173
+ this.update();
174
+ }
175
+ }
176
+ } catch (error) {
177
+ console.warn(`Invalid selector: ${query}`, error);
110
178
  }
111
- } catch (error) {
112
- console.warn(`Invalid selector: ${query}`, error);
113
- }
179
+ });
114
180
  });
115
- });
116
- this.EAObserver.start("select");
181
+ }
182
+
183
+ this.EAObserver.connect("select");
117
184
  }
118
185
 
119
186
  /**
@@ -132,19 +199,30 @@ export class Selective {
132
199
  } else if (target instanceof HTMLSelectElement) {
133
200
  this.destroyElement(target);
134
201
  }
202
+
203
+ // Trigger update after partial destroy
204
+ if (target !== null && this.is(LifecycleState.MOUNTED)) {
205
+ this.update();
206
+ }
135
207
  }
136
208
 
137
209
  /**
138
210
  * Destroys all bound Selective instances and clears bindings/state.
139
211
  * Stops the ElementAdditionObserver.
212
+ * Calls lifecycle destroy.
140
213
  */
141
214
  private destroyAll(): void {
215
+ if (this.state === LifecycleState.DESTROYED) return;
216
+
142
217
  const bindedCommands = Libs.getBindedCommand();
143
218
  bindedCommands.forEach((query: string) => this.destroyByQuery(query));
144
219
 
145
220
  this.bindedQueries.clear();
146
221
  Libs.getBindedCommand().length = 0;
147
- this.EAObserver?.stop();
222
+ this.EAObserver?.disconnect();
223
+
224
+ // Call parent lifecycle destroy
225
+ super.destroy();
148
226
  }
149
227
 
150
228
  /**
@@ -176,20 +254,20 @@ export class Selective {
176
254
  const bindMap = Libs.getBinderMap(selectElement) as BinderMap | null;
177
255
  if (!bindMap) return;
178
256
 
179
- const popup = bindMap.container?.popup as Popup | null;
180
- popup?.detroy();
257
+ const selfBox = bindMap.self as SelectBox | null;
181
258
 
182
259
  Libs.setUnbinderMap(selectElement, bindMap);
183
260
 
184
261
  const wasObserving = !!this.EAObserver;
185
- if (wasObserving) this.EAObserver?.stop();
262
+ if (wasObserving) this.EAObserver?.disconnect();
186
263
 
187
264
  try {
188
265
  bindMap.self?.deInit?.();
189
- } catch (_) { }
266
+ } catch (_) {}
190
267
 
191
268
  const wrapper: HTMLElement | null =
192
- (bindMap.container?.element as HTMLElement | undefined) ?? selectElement.parentElement;
269
+ (bindMap.container?.element as HTMLElement | undefined) ??
270
+ selectElement.parentElement;
193
271
 
194
272
  selectElement.style.display = "";
195
273
  selectElement.style.visibility = "";
@@ -199,14 +277,16 @@ export class Selective {
199
277
  if (wrapper && wrapper.parentNode) {
200
278
  wrapper.parentNode.replaceChild(selectElement, wrapper);
201
279
  } else {
202
- document.body.appendChild(selectElement);
280
+ selectElement.appendChild(selectElement);
203
281
  }
204
282
 
205
283
  Libs.removeBinderMap(selectElement);
206
284
 
207
285
  if (wasObserving && this.bindedQueries.size > 0) {
208
- this.EAObserver?.start("select");
286
+ this.EAObserver?.connect("select");
209
287
  }
288
+
289
+ selfBox?.destroy?.();
210
290
  }
211
291
 
212
292
  /**
@@ -219,6 +299,11 @@ export class Selective {
219
299
  public rebind(query: string, options: SelectiveOptions): void {
220
300
  this.destroyByQuery(query);
221
301
  this.bind(query, options);
302
+
303
+ // Trigger update after rebind
304
+ if (this.is(LifecycleState.MOUNTED)) {
305
+ this.update();
306
+ }
222
307
  }
223
308
 
224
309
  /**
@@ -230,13 +315,22 @@ export class Selective {
230
315
  * @param {object} options - Configuration used for this instance.
231
316
  * @returns {boolean} - False if already bound; true if successfully applied.
232
317
  */
233
- private applySelectBox(selectElement: HTMLSelectElement, options: SelectiveOptions): boolean {
234
- if (Libs.getBinderMap(selectElement) || Libs.getUnbinderMap(selectElement)) {
318
+ private applySelectBox(
319
+ selectElement: HTMLSelectElement,
320
+ options: SelectiveOptions,
321
+ ): boolean {
322
+ if (
323
+ Libs.getBinderMap(selectElement) ||
324
+ Libs.getUnbinderMap(selectElement)
325
+ ) {
235
326
  return false;
236
327
  }
237
328
 
238
329
  const SEID = Libs.randomString(8);
239
- const options_cfg = Libs.buildConfig(selectElement, options) as SelectiveOptions;
330
+ const options_cfg = Libs.buildConfig(
331
+ selectElement,
332
+ options,
333
+ ) as SelectiveOptions;
240
334
 
241
335
  options_cfg.SEID = SEID;
242
336
  options_cfg.SEID_LIST = `seui-${SEID}-optionlist`;
@@ -245,15 +339,24 @@ export class Selective {
245
339
  const bindMap: BinderMap = { options: options_cfg };
246
340
  Libs.setBinderMap(selectElement, bindMap);
247
341
 
342
+ // Create SelectBox with lifecycle
248
343
  const selectBox = new SelectBox(selectElement, this);
344
+
345
+ selectBox.on("onMount", () => {
346
+ if (selectBox.container.view) {
347
+ selectBox.container.view.addEventListener("mouseup", () => {
348
+ bindMap.action?.toggle?.();
349
+ });
350
+ }
351
+ });
352
+
353
+ // Mount the SelectBox
354
+ selectBox.mount();
355
+
249
356
  bindMap.container = selectBox.container;
250
357
  bindMap.action = selectBox.getAction();
251
358
  bindMap.self = selectBox;
252
359
 
253
- selectBox.container.view.addEventListener("mouseup", () => {
254
- bindMap.action?.toggle?.();
255
- });
256
-
257
360
  return true;
258
361
  }
259
362
 
@@ -268,11 +371,17 @@ export class Selective {
268
371
  * @param {*} action - The object containing the property.
269
372
  * @returns {PropertiesType} - The derived property type and name.
270
373
  */
271
- private getProperties(actionName: string, action: Record<string, any>): PropertiesType {
374
+ private getProperties(
375
+ actionName: string,
376
+ action: Record<string, any>,
377
+ ): PropertiesType {
272
378
  const descriptor = Object.getOwnPropertyDescriptor(action, actionName);
273
379
  let type: PropertiesType["type"] = "variable";
274
380
 
275
- if (descriptor?.get || (descriptor?.set && typeof action[actionName] !== "function")) {
381
+ if (
382
+ descriptor?.get ||
383
+ (descriptor?.set && typeof action[actionName] !== "function")
384
+ ) {
276
385
  type = "get-set";
277
386
  } else if (typeof action[actionName] === "function") {
278
387
  type = "func";
@@ -289,7 +398,11 @@ export class Selective {
289
398
  * @param {string} name - The property name to expose.
290
399
  * @param {HTMLElement[]} els - The list of bound elements to proxy.
291
400
  */
292
- private buildGetSetAction(object: Record<string, any>, name: string, els: HTMLElement[]): void {
401
+ private buildGetSetAction(
402
+ object: Record<string, any>,
403
+ name: string,
404
+ els: HTMLElement[],
405
+ ): void {
293
406
  Object.defineProperty(object, name, {
294
407
  get() {
295
408
  const binded = Libs.getBinderMap(els[0]) as BinderMap;
@@ -315,7 +428,11 @@ export class Selective {
315
428
  * @param {string} name - The function name to expose.
316
429
  * @param {HTMLElement[]} els - The list of bound elements to invoke against.
317
430
  */
318
- private buildFuntionAction(object: Record<string, any>, name: string, els: HTMLElement[]): void {
431
+ private buildFuntionAction(
432
+ object: Record<string, any>,
433
+ name: string,
434
+ els: HTMLElement[],
435
+ ): void {
319
436
  object[name] = (...params: any[]) => {
320
437
  let resp = null;
321
438
  for (let index = 0; index < els.length; index++) {
@@ -331,4 +448,4 @@ export class Selective {
331
448
  return resp ?? object;
332
449
  };
333
450
  }
334
- }
451
+ }