selective-ui 1.2.4 → 1.2.5

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 (44) hide show
  1. package/dist/selective-ui.esm.js +4174 -1237
  2. package/dist/selective-ui.esm.js.map +1 -1
  3. package/dist/selective-ui.esm.min.js +2 -2
  4. package/dist/selective-ui.esm.min.js.br +0 -0
  5. package/dist/selective-ui.min.js +2 -2
  6. package/dist/selective-ui.min.js.br +0 -0
  7. package/dist/selective-ui.umd.js +4175 -1238
  8. package/dist/selective-ui.umd.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/ts/adapter/mixed-adapter.ts +247 -91
  11. package/src/ts/components/accessorybox.ts +164 -67
  12. package/src/ts/components/directive.ts +53 -24
  13. package/src/ts/components/option-handle.ts +121 -54
  14. package/src/ts/components/placeholder.ts +70 -32
  15. package/src/ts/components/popup/empty-state.ts +68 -32
  16. package/src/ts/components/popup/loading-state.ts +70 -30
  17. package/src/ts/components/popup/popup.ts +0 -1
  18. package/src/ts/components/searchbox.ts +185 -46
  19. package/src/ts/components/selectbox.ts +309 -30
  20. package/src/ts/core/base/adapter.ts +158 -77
  21. package/src/ts/core/base/fenwick.ts +147 -0
  22. package/src/ts/core/base/lifecycle.ts +118 -35
  23. package/src/ts/core/base/model.ts +94 -36
  24. package/src/ts/core/base/recyclerview.ts +0 -1
  25. package/src/ts/core/base/view.ts +54 -23
  26. package/src/ts/core/base/virtual-recyclerview.ts +360 -278
  27. package/src/ts/core/model-manager.ts +162 -81
  28. package/src/ts/core/search-controller.ts +164 -91
  29. package/src/ts/global.ts +1 -1
  30. package/src/ts/index.ts +1 -1
  31. package/src/ts/models/group-model.ts +138 -32
  32. package/src/ts/models/option-model.ts +184 -48
  33. package/src/ts/services/dataset-observer.ts +72 -10
  34. package/src/ts/services/ea-observer.ts +87 -10
  35. package/src/ts/services/effector.ts +181 -32
  36. package/src/ts/services/refresher.ts +30 -6
  37. package/src/ts/services/resize-observer.ts +132 -15
  38. package/src/ts/services/select-observer.ts +115 -50
  39. package/src/ts/types/utils/ievents.type.ts +6 -1
  40. package/src/ts/utils/callback-scheduler.ts +112 -34
  41. package/src/ts/utils/ievents.ts +91 -29
  42. package/src/ts/utils/selective.ts +330 -61
  43. package/src/ts/views/group-view.ts +137 -26
  44. package/src/ts/views/option-view.ts +262 -50
@@ -9,78 +9,145 @@ import { SelectiveOptions } from "../types/utils/selective.type";
9
9
  import { LifecycleState } from "../types/core/base/lifecycle.type";
10
10
 
11
11
  /**
12
- * @extends {Model<HTMLOptGroupElement, GroupViewTags, GroupView>}
12
+ * Domain model for a native `<optgroup>` element.
13
+ *
14
+ * This model represents a **group header** plus its **child options** and is used by
15
+ * adapters/recyclers to render grouped lists (e.g., {@link GroupView} + {@link OptionModel} rows).
16
+ *
17
+ * ### Responsibility
18
+ * - Mirror and synchronize state derived from the backing `<optgroup>`:
19
+ * - `label` from `optgroup.label`
20
+ * - `collapsed` from `optgroup.dataset.collapsed` (string → boolean)
21
+ * - Own and manage the group’s child {@link OptionModel} collection, including back-references.
22
+ * - Provide derived selectors for consumer logic: `value`, `selectedItems`, `visibleItems`, `hasVisibleItems`.
23
+ * - Emit collapsed-state change notifications to subscribers via `onCollapsedChanged(...)`.
24
+ *
25
+ * ### Lifecycle (Strict FSM)
26
+ * - Constructor delegates to {@link Model} and initializes base lifecycle (`NEW → INITIALIZED`).
27
+ * - {@link init} reads initial state from the target element and transitions to `MOUNTED`.
28
+ * - {@link update} keeps the attached {@link GroupView} in sync (label + collapsed state).
29
+ * - {@link destroy} destroys all child options and transitions to `DESTROYED` (idempotent).
30
+ *
31
+ * ### Relationships
32
+ * - **Model ↔ View**: `view` (when assigned) is a {@link GroupView} responsible for DOM updates.
33
+ * - **Group ↔ Options**: `items` contains child {@link OptionModel}s; each option holds `option.group`.
34
+ * - **Adapter/Recycler**: binders (e.g., MixedAdapter) call `addItem/removeItem`, set `view`,
35
+ * and invoke {@link updateVisibility} based on filtering/virtualization outcomes.
36
+ *
37
+ * ### Events / Hooks
38
+ * - Collapsed changes are dispatched through {@link iEvents.callEvent} to callbacks registered via
39
+ * {@link onCollapsedChanged}. These callbacks receive an `evtToken` for iEvents chaining/cancellation,
40
+ * the model, and the new collapsed state.
41
+ *
42
+ * @extends {Model<HTMLOptGroupElement, GroupViewTags, GroupView, SelectiveOptions>}
43
+ * @see {@link OptionModel}
44
+ * @see {@link GroupView}
13
45
  */
14
46
  export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupView, SelectiveOptions> {
47
+ /** Group label (mirrors `HTMLSelectOptGroupElement.label`). */
15
48
  public label = "";
16
49
 
50
+ /**
51
+ * Child option models that belong to this group.
52
+ *
53
+ * Ownership: this group destroys its children in {@link destroy}.
54
+ */
17
55
  public items: OptionModel[] = [];
18
56
 
57
+ /**
58
+ * Whether this group is collapsed.
59
+ *
60
+ * Source-of-truth:
61
+ * - Initialized from `targetElement.dataset.collapsed` (string → boolean).
62
+ * - Toggled via {@link toggleCollapse}.
63
+ */
19
64
  public collapsed = false;
20
65
 
66
+ /**
67
+ * Subscribers invoked when collapsed state changes.
68
+ * Callbacks are invoked through {@link iEvents.callEvent}.
69
+ */
21
70
  private privOnCollapsedChanged: Array<(evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void> = [];
22
71
 
23
72
  /**
24
- * Initializes a group model with options and an optional <optgroup> target.
25
- * Reads the label and collapsed state from the target element's attributes/dataset.
73
+ * Creates a group model from configuration and an optional `<optgroup>` element.
26
74
  *
27
- * @param {SelectiveOptions} options - Configuration for the model.
28
- * @param {HTMLOptGroupElement} [targetElement] - The source <optgroup> element.
75
+ * @param {SelectiveOptions} options - Shared configuration for models/views.
76
+ * @param {HTMLOptGroupElement} [targetElement] - Backing `<optgroup>` element (when available).
29
77
  */
30
78
  public constructor(options: SelectiveOptions, targetElement?: HTMLOptGroupElement) {
31
79
  super(options, targetElement ?? null, null);
80
+ this.label = this.targetElement.label;
32
81
  }
33
-
34
- public override init() {
35
- if (this.targetElement) {
36
- this.label = this.targetElement.label;
37
- this.collapsed = Libs.string2Boolean(this.targetElement.dataset?.collapsed);
38
- }
82
+
83
+ /**
84
+ * Initializes group state from the backing `<optgroup>` (if present) and mounts the model.
85
+ *
86
+ * Behavior:
87
+ * - Reads `label` from `targetElement.label`.
88
+ * - Reads `collapsed` from `targetElement.dataset.collapsed` via {@link Libs.string2Boolean}.
89
+ * - Calls `super.init()` then transitions to `MOUNTED` via `mount()`.
90
+ *
91
+ * Idempotency:
92
+ * - Base {@link Model}/{@link Lifecycle} guards prevent duplicate `init()` transitions.
93
+ *
94
+ * @returns {void}
95
+ * @override
96
+ */
97
+ public override init(): void {
98
+ this.collapsed = Libs.string2Boolean(this.targetElement.dataset?.collapsed);
39
99
 
40
100
  super.init();
41
101
  this.mount();
42
102
  }
43
103
 
44
104
  /**
45
- * Returns the array of values from all option items within the group.
105
+ * Returns all option values within this group.
46
106
  *
47
- * @type {string[]}
107
+ * @returns {string[]} Values of all child options (in current `items` order).
48
108
  */
49
109
  public get value(): string[] {
50
110
  return this.items.map((item) => item.value);
51
111
  }
52
112
 
53
113
  /**
54
- * Returns the list of option items currently selected within the group.
114
+ * Returns the subset of child options that are currently selected.
55
115
  *
56
- * @type {OptionModel[]}
116
+ * @returns {OptionModel[]} Selected child options.
57
117
  */
58
118
  public get selectedItems(): OptionModel[] {
59
119
  return this.items.filter((item) => item.selected);
60
120
  }
61
121
 
62
122
  /**
63
- * Returns the list of option items currently visible within the group.
123
+ * Returns the subset of child options that are currently visible.
64
124
  *
65
- * @type {OptionModel[]}
125
+ * Visibility is typically controlled by filtering/search (e.g., toggling `OptionModel.visible`).
126
+ *
127
+ * @returns {OptionModel[]} Visible child options.
66
128
  */
67
129
  public get visibleItems(): OptionModel[] {
68
130
  return this.items.filter((item) => item.visible);
69
131
  }
70
132
 
71
133
  /**
72
- * Indicates whether the group has at least one visible option item.
134
+ * Whether the group has at least one visible option.
73
135
  *
74
- * @type {boolean}
136
+ * @returns {boolean} True if any child option is visible.
75
137
  */
76
138
  public get hasVisibleItems(): boolean {
77
139
  return this.visibleItems.length > 0;
78
140
  }
79
141
 
80
142
  /**
81
- * Updates the group's label from the new target element and propagates the change to the view.
143
+ * Rebinds this model to a new `<optgroup>` element and synchronizes the label immediately.
144
+ *
145
+ * Notes:
146
+ * - This method updates the label and pushes it to the view, then triggers a lifecycle update.
147
+ * - The signature is intentionally stricter than the base model (`HTMLOptGroupElement` only).
82
148
  *
83
- * @param {HTMLOptGroupElement} targetElement - The updated <optgroup> element.
149
+ * @param {HTMLOptGroupElement} targetElement - Updated backing `<optgroup>` element.
150
+ * @returns {void}
84
151
  */
85
152
  public updateTarget(targetElement: HTMLOptGroupElement): void {
86
153
  this.label = targetElement.label;
@@ -89,8 +156,14 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
89
156
  }
90
157
 
91
158
  /**
92
- * Hook invoked when the target element reference changes.
93
- * Updates the view's label and collapsed state to keep UI in sync.
159
+ * Synchronizes the attached view (if any) with current model state and emits lifecycle update.
160
+ *
161
+ * View sync:
162
+ * - Updates header label
163
+ * - Applies collapsed state
164
+ *
165
+ * @returns {void}
166
+ * @override
94
167
  */
95
168
  public override update(): void {
96
169
  if (this.view) {
@@ -100,6 +173,18 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
100
173
  super.update();
101
174
  }
102
175
 
176
+ /**
177
+ * Destroys the group model and releases owned resources.
178
+ *
179
+ * Behavior:
180
+ * - Idempotent once lifecycle is {@link LifecycleState.DESTROYED}.
181
+ * - Destroys all child {@link OptionModel} instances.
182
+ * - Clears the `items` array.
183
+ * - Completes lifecycle teardown via `super.destroy()`.
184
+ *
185
+ * @returns {void}
186
+ * @override
187
+ */
103
188
  public override destroy(): void {
104
189
  if (this.is(LifecycleState.DESTROYED)) {
105
190
  return;
@@ -114,16 +199,27 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
114
199
  }
115
200
 
116
201
  /**
117
- * Registers a callback to be invoked when the group's collapsed state changes.
202
+ * Subscribes to changes in the group's collapsed state.
118
203
  *
119
- * @param {(evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void} callback - Listener for collapse changes.
204
+ * Callbacks are invoked from {@link toggleCollapse} via {@link iEvents.callEvent}.
205
+ *
206
+ * @param {(evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void} callback
207
+ * Listener invoked with `(evtToken, model, collapsed)`.
208
+ * @returns {void}
120
209
  */
121
210
  public onCollapsedChanged(callback: (evtToken: IEventCallback, model: GroupModel, collapsed: boolean) => void): void {
122
211
  this.privOnCollapsedChanged.push(callback);
123
212
  }
124
213
 
125
214
  /**
126
- * Toggles the group's collapsed state, updates the view, and notifies registered listeners.
215
+ * Toggles collapsed state, updates the view, and notifies subscribers.
216
+ *
217
+ * Side effects:
218
+ * - Mutates {@link collapsed}.
219
+ * - Calls `view.setCollapsed(...)` if a view is attached.
220
+ * - Dispatches callbacks registered via {@link onCollapsedChanged}.
221
+ *
222
+ * @returns {void}
127
223
  */
128
224
  public toggleCollapse(): void {
129
225
  this.collapsed = !this.collapsed;
@@ -133,9 +229,10 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
133
229
  }
134
230
 
135
231
  /**
136
- * Adds an option item to this group and sets its back-reference to the group.
232
+ * Adds a child option to this group and sets the option's back-reference.
137
233
  *
138
- * @param {OptionModel} optionModel - The option to add.
234
+ * @param {OptionModel} optionModel - Option to add.
235
+ * @returns {void}
139
236
  */
140
237
  public addItem(optionModel: OptionModel): void {
141
238
  this.items.push(optionModel);
@@ -143,9 +240,12 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
143
240
  }
144
241
 
145
242
  /**
146
- * Removes an option item from this group and clears its group reference.
243
+ * Removes a child option from this group and clears the option's back-reference.
244
+ *
245
+ * No-op if the option is not present in {@link items}.
147
246
  *
148
- * @param {OptionModel} optionModel - The option to remove.
247
+ * @param {OptionModel} optionModel - Option to remove.
248
+ * @returns {void}
149
249
  */
150
250
  public removeItem(optionModel: OptionModel): void {
151
251
  const index = this.items.indexOf(optionModel);
@@ -156,8 +256,14 @@ export class GroupModel extends Model<HTMLOptGroupElement, GroupViewTags, GroupV
156
256
  }
157
257
 
158
258
  /**
159
- * Updates the group's visibility in the view, typically based on children visibility.
160
- * No-ops if the view is not initialized.
259
+ * Requests the attached view (if any) to recompute/update its visibility.
260
+ *
261
+ * Typically called after child visibility changes (filter/search) so the group header can
262
+ * reflect whether it contains visible items.
263
+ *
264
+ * No-op if no view is attached.
265
+ *
266
+ * @returns {void}
161
267
  */
162
268
  public updateVisibility(): void {
163
269
  this.view?.updateVisibility();
@@ -10,33 +10,109 @@ import { SelectiveOptions } from "../types/utils/selective.type";
10
10
  import { LifecycleState } from "../types/core/base/lifecycle.type";
11
11
 
12
12
  /**
13
+ * Domain model for a native `<option>` element.
14
+ *
15
+ * This is the core selectable row model consumed by adapters/recyclers. It mirrors the backing
16
+ * `<option>` node while also carrying UI-only state used by the headless+DOM-driven layer
17
+ * (visibility, highlight, precomputed search key).
18
+ *
19
+ * ### Responsibility
20
+ * - Mirror and synchronize state with the backing `<option>` element:
21
+ * - `value`, `selected`, `dataset`, and display label (with optional tag translation / HTML policy).
22
+ * - Provide derived properties for rendering:
23
+ * - image resolution (`imageSrc` / `hasImage`),
24
+ * - rich/stripped label (`text` / `textContent`),
25
+ * - normalized search key (`textToFind`).
26
+ * - Maintain UI-only flags:
27
+ * - `visible` for filtering/search,
28
+ * - `highlighted` for keyboard navigation/hover.
29
+ * - Publish change notifications:
30
+ * - **External** selection (`selected`) vs **internal** selection sync (`selectedNonTrigger`),
31
+ * - visibility changes (`visible`).
32
+ *
33
+ * ### Lifecycle (Strict FSM)
34
+ * - Base {@link Model} calls `init()` during construction (`NEW → INITIALIZED`), and this subclass
35
+ * overrides {@link init} to precompute {@link textToFind} before delegating to `super.init()`.
36
+ * - {@link init} then transitions to `MOUNTED` via `mount()` for first render readiness.
37
+ * - {@link update} recomputes derived text/search fields and pushes state into the {@link OptionView}
38
+ * if attached, then emits lifecycle update.
39
+ * - {@link destroy} clears listeners/references and transitions to `DESTROYED` (idempotent).
40
+ *
41
+ * ### External vs internal selection semantics
42
+ * - `selected` is the **external** user-facing signal:
43
+ * updates state (via {@link selectedNonTrigger}) and then notifies {@link onSelected} listeners.
44
+ * - `selectedNonTrigger` is the **internal** sync signal:
45
+ * updates view/DOM/backing `<option>` and then notifies {@link onInternalSelected} listeners
46
+ * **without** implying user intent.
47
+ *
48
+ * ### DOM & a11y side effects (when a view is attached)
49
+ * - Toggles CSS classes: `"hide"`, `"highlight"`, `"checked"`.
50
+ * - Updates `aria-selected` on the option row root element.
51
+ * - Updates label content (either `innerHTML` or `textContent` depending on `allowHtml`).
52
+ * - Mirrors selection state to the backing `<option>` (property + attribute).
53
+ *
13
54
  * @extends {Model<HTMLOptionElement, OptionViewTags, OptionView, SelectiveOptions>}
55
+ * @see {@link GroupModel}
56
+ * @see {@link OptionView}
14
57
  */
15
58
  export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, OptionView, SelectiveOptions> {
59
+ /**
60
+ * External selection subscribers (emitted by the {@link selected} setter).
61
+ * Use this for user-facing selection flows.
62
+ */
16
63
  private privOnSelected: Array<(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void> = [];
17
64
 
65
+ /**
66
+ * Internal selection subscribers (emitted by the {@link selectedNonTrigger} setter).
67
+ * Use this for silent synchronization flows.
68
+ */
18
69
  private privOnInternalSelected: Array<(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void> = [];
19
70
 
71
+ /**
72
+ * Visibility subscribers (emitted by the {@link visible} setter).
73
+ * Commonly used to recompute group visibility and update aggregated visibility stats.
74
+ */
20
75
  private privOnVisibilityChanged: Array<(evtToken: IEventCallback, model: OptionModel, visible: boolean) => void> = [];
21
76
 
77
+ /**
78
+ * Visibility flag used for filtering/search.
79
+ * When `false`, adapters/recyclers may treat this item as non-renderable.
80
+ */
22
81
  private _visible = true;
23
82
 
83
+ /** Highlight flag used for keyboard navigation / hover. */
24
84
  private _highlighted = false;
25
85
 
86
+ /**
87
+ * Parent group model (if this option belongs to a group).
88
+ * Assigned by grouping logic (e.g., GroupModel/MixedAdapter).
89
+ */
26
90
  public group: GroupModel | null = null;
27
91
 
28
92
  /**
29
- * Constructs a Model instance with configuration options and optional bindings to a target element and view.
30
- * Stores references for later updates and rendering.
93
+ * Creates an option model.
31
94
  *
32
- * @param {SelectiveOptions} options - Configuration options for the model.
33
- * @param {HTMLOptionElement|null} [targetElement=null] - The underlying element (e.g., <option> or group node).
34
- * @param {OptionView|null} [view=null] - The associated view responsible for rendering the model.
95
+ * @param {SelectiveOptions} options - Shared configuration for models/views.
96
+ * @param {HTMLOptionElement | null} [targetElement=null] - Backing `<option>` element.
97
+ * @param {OptionView | null} [view=null] - Optional view used to render this model.
35
98
  */
36
- public constructor(options: SelectiveOptions, targetElement: HTMLOptionElement | null = null, view: OptionView | null = null) {
99
+ public constructor(
100
+ options: SelectiveOptions,
101
+ targetElement: HTMLOptionElement | null = null,
102
+ view: OptionView | null = null
103
+ ) {
37
104
  super(options, targetElement, view);
38
105
  }
39
106
 
107
+ /**
108
+ * Initializes the model and precomputes the search key.
109
+ *
110
+ * - Computes {@link textToFind} from {@link textContent} (lowercased + normalized).
111
+ * - Delegates to `super.init()` and then transitions to `MOUNTED` via `mount()`.
112
+ *
113
+ * @returns {void}
114
+ * @override
115
+ */
40
116
  public override init(): void {
41
117
  this.textToFind = Libs.string2normalize(this.textContent.toLowerCase());
42
118
 
@@ -45,46 +121,50 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
45
121
  }
46
122
 
47
123
  /**
48
- * Returns the image source from dataset (imgsrc or image), or an empty string if absent.
124
+ * Image source resolved from dataset (`imgsrc` or `image`), or empty string if absent.
49
125
  *
50
- * @type {string}
126
+ * @returns {string}
51
127
  */
52
128
  public get imageSrc(): string {
53
129
  return this.dataset?.imgsrc || this.dataset?.image || "";
54
130
  }
55
131
 
56
132
  /**
57
- * Indicates whether this option has an associated image source.
133
+ * Whether this option has an image associated with it.
58
134
  *
59
- * @type {boolean}
135
+ * @returns {boolean}
60
136
  */
61
137
  public get hasImage(): boolean {
62
138
  return !!this.imageSrc;
63
139
  }
64
140
 
65
141
  /**
66
- * Gets the option's current value from the underlying <option> element.
142
+ * Current value of the backing `<option>`.
67
143
  *
68
- * @type {string}
144
+ * @returns {string}
69
145
  */
70
146
  public get value(): string {
71
147
  return this.targetElement?.value ?? "";
72
148
  }
73
149
 
74
150
  /**
75
- * Gets whether the option is currently selected (proxied to the <option> element).
151
+ * Whether the backing `<option>` is selected.
76
152
  *
77
- * @type {boolean}
153
+ * @returns {boolean}
78
154
  */
79
155
  public get selected(): boolean {
80
156
  return !!this.targetElement?.selected;
81
157
  }
82
158
 
83
159
  /**
84
- * Sets the selected state and triggers external selection listeners.
85
- * Uses selectedNonTrigger internally to update DOM/ARIA without firing external side effects first.
160
+ * Sets selected state and emits **external** selection listeners.
86
161
  *
87
- * @type {boolean}
162
+ * Flow:
163
+ * - Delegates to {@link selectedNonTrigger} to synchronize view/DOM/backing element.
164
+ * - Notifies {@link onSelected} subscribers via {@link iEvents.callEvent}.
165
+ *
166
+ * @param {boolean} value - New selection state.
167
+ * @returns {void}
88
168
  */
89
169
  public set selected(value: boolean) {
90
170
  this.selectedNonTrigger = value;
@@ -92,18 +172,25 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
92
172
  }
93
173
 
94
174
  /**
95
- * Gets whether the option is currently visible in the UI.
175
+ * Whether this option is visible (used for filtering/search).
96
176
  *
97
- * @type {boolean}
177
+ * @returns {boolean}
98
178
  */
99
179
  public get visible(): boolean {
100
180
  return this._visible;
101
181
  }
102
182
 
103
183
  /**
104
- * Sets the visibility state; toggles "hide" class on the view and notifies visibility listeners.
184
+ * Sets visibility and synchronizes the view (if attached), then emits visibility listeners.
185
+ *
186
+ * Side effects (when view attached):
187
+ * - Toggles `"hide"` CSS class on the view root element.
105
188
  *
106
- * @type {boolean}
189
+ * Idempotent:
190
+ * - No-op if the new value equals the current state.
191
+ *
192
+ * @param {boolean} value - New visibility state.
193
+ * @returns {void}
107
194
  */
108
195
  public set visible(value: boolean) {
109
196
  if (this._visible === value) return;
@@ -116,19 +203,27 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
116
203
  }
117
204
 
118
205
  /**
119
- * Gets the selected state without triggering external listeners (alias of selected).
206
+ * Reads selected state without emitting external selection listeners.
120
207
  *
121
- * @type {boolean}
208
+ * @returns {boolean}
122
209
  */
123
210
  public get selectedNonTrigger(): boolean {
124
211
  return this.selected;
125
212
  }
126
213
 
127
214
  /**
128
- * Sets the selected state and updates input checked, CSS classes, ARIA attributes,
129
- * and the underlying <option> 'selected' attribute. Notifies internal selection listeners.
215
+ * Sets selected state **silently** (internal sync), updates view/a11y/backing DOM, then emits internal listeners.
216
+ *
217
+ * Side effects (when view/backing element exist):
218
+ * - Updates the input checked state (`OptionInput`) if present.
219
+ * - Toggles `"checked"` class on the root element.
220
+ * - Sets `aria-selected`.
221
+ * - Mirrors to backing `<option>`:
222
+ * - toggles `selected` attribute,
223
+ * - sets `targetElement.selected`.
130
224
  *
131
- * @type {boolean}
225
+ * @param {boolean} value - New selection state.
226
+ * @returns {void}
132
227
  */
133
228
  public set selectedNonTrigger(value: boolean) {
134
229
  const input = this.view?.view?.tags?.OptionInput;
@@ -148,10 +243,16 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
148
243
  }
149
244
 
150
245
  /**
151
- * Returns the display text for the option, applying tag translation and optional HTML allowance.
152
- * If allowHtml=false, returns stripped/sanitized text.
246
+ * Display label for rendering (with tag translation and HTML policy).
247
+ *
248
+ * Source precedence:
249
+ * - `dataset.mask` if present, otherwise `targetElement.text`.
250
+ *
251
+ * Policy:
252
+ * - When `options.allowHtml === true`, returns translated HTML.
253
+ * - When `options.allowHtml === false`, returns plain text (HTML stripped).
153
254
  *
154
- * @type {string}
255
+ * @returns {string}
155
256
  */
156
257
  public get text(): string {
157
258
  const raw = this.dataset?.mask ?? this.targetElement?.text ?? "";
@@ -160,40 +261,49 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
160
261
  }
161
262
 
162
263
  /**
163
- * Returns a plain-text version of the display text (trimmed),
164
- * stripping HTML if allowHtml is true, otherwise the raw text.
264
+ * Plain-text version of the display label, trimmed.
165
265
  *
166
- * @type {string}
266
+ * - If `allowHtml` is enabled, strips HTML from {@link text}.
267
+ * - Otherwise returns {@link text} directly (already plain).
268
+ *
269
+ * @returns {string}
167
270
  */
168
271
  public get textContent(): string {
169
272
  return this.options.allowHtml ? Libs.stripHtml(this.text).trim() : this.text.trim();
170
273
  }
171
274
 
275
+ /**
276
+ * Normalized, lowercase search key used for filtering/search.
277
+ * Computed during {@link init} and recomputed in {@link update}.
278
+ */
172
279
  public textToFind: string;
173
280
 
174
281
  /**
175
- * Returns the dataset object of the underlying <option> element, or an empty object.
282
+ * Dataset object of the backing `<option>` element.
176
283
  *
177
- * @type {DOMStringMap|Record<string, string>}
284
+ * @returns {DOMStringMap}
178
285
  */
179
286
  public get dataset(): DOMStringMap {
180
287
  return this.targetElement?.dataset ?? ({} as DOMStringMap);
181
288
  }
182
289
 
183
290
  /**
184
- * Gets whether the option is currently highlighted (e.g., via keyboard navigation).
291
+ * Whether this option is currently highlighted (navigation/hover).
185
292
  *
186
- * @type {boolean}
293
+ * @returns {boolean}
187
294
  */
188
295
  public get highlighted(): boolean {
189
296
  return this._highlighted;
190
297
  }
191
298
 
192
299
  /**
193
- * Sets the highlighted state and toggles the "highlight" CSS class on the view.
194
- * Always syncs the DOM class even if the state is unchanged.
300
+ * Sets highlight state and synchronizes the view (if attached).
301
+ *
302
+ * Side effects:
303
+ * - Toggles `"highlight"` CSS class on the view root element.
195
304
  *
196
- * @type {boolean}
305
+ * @param {boolean} value - New highlight state.
306
+ * @returns {void}
197
307
  */
198
308
  public set highlighted(value: boolean) {
199
309
  const val = !!value;
@@ -204,36 +314,50 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
204
314
  }
205
315
 
206
316
  /**
207
- * Registers a listener invoked when external selection changes (via setter `selected`).
317
+ * Subscribes to **external** selection changes (emitted by {@link selected}).
208
318
  *
209
- * @param {(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void} callback - Selection listener.
319
+ * @param {(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void} callback - Listener callback.
320
+ * @returns {void}
210
321
  */
211
322
  public onSelected(callback: (evtToken: IEventCallback, el: OptionModel, selected: boolean) => void): void {
212
323
  this.privOnSelected.push(callback);
213
324
  }
214
325
 
215
326
  /**
216
- * Registers a listener invoked when internal selection changes (via setter `selectedNonTrigger`).
327
+ * Subscribes to **internal** selection changes (emitted by {@link selectedNonTrigger}).
217
328
  *
218
- * @param {(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void} callback - Internal selection listener.
329
+ * @param {(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void} callback - Listener callback.
330
+ * @returns {void}
219
331
  */
220
332
  public onInternalSelected(callback: (evtToken: IEventCallback, el: OptionModel, selected: boolean) => void): void {
221
333
  this.privOnInternalSelected.push(callback);
222
334
  }
223
335
 
224
336
  /**
225
- * Registers a listener invoked when visibility changes (via setter `visible`).
337
+ * Subscribes to visibility changes (emitted by {@link visible}).
226
338
  *
227
- * @param {(evtToken: IEventCallback, model: OptionModel, visible: boolean) => void} callback - Visibility listener.
339
+ * @param {(evtToken: IEventCallback, model: OptionModel, visible: boolean) => void} callback - Listener callback.
340
+ * @returns {void}
228
341
  */
229
342
  public onVisibilityChanged(callback: (evtToken: IEventCallback, model: OptionModel, visible: boolean) => void): void {
230
343
  this.privOnVisibilityChanged.push(callback);
231
344
  }
232
345
 
233
346
  /**
234
- * Hook called when the target <option> element changes.
235
- * Updates label content (HTML or text), image src/alt if present,
236
- * and synchronizes initial selected state to the view.
347
+ * Synchronizes derived fields and the attached view from the current backing element/options.
348
+ *
349
+ * Syncs:
350
+ * - {@link textToFind} (normalized search key)
351
+ * - Label content:
352
+ * - `innerHTML` when `allowHtml` is enabled,
353
+ * - otherwise `textContent`
354
+ * - Image attributes (`src`/`alt`) when present
355
+ * - Selected state from `targetElement.selected` via {@link selectedNonTrigger}
356
+ *
357
+ * No-op for view updates when no view is attached; still emits lifecycle update via `super.update()`.
358
+ *
359
+ * @returns {void}
360
+ * @override
237
361
  */
238
362
  public override update(): void {
239
363
  this.textToFind = Libs.string2normalize(this.textContent.toLowerCase());
@@ -262,6 +386,18 @@ export class OptionModel extends Model<HTMLOptionElement, OptionViewTags, Option
262
386
  super.update();
263
387
  }
264
388
 
389
+ /**
390
+ * Destroys the model and releases listener references.
391
+ *
392
+ * Behavior:
393
+ * - Idempotent once lifecycle is {@link LifecycleState.DESTROYED}.
394
+ * - Clears external/internal selection listeners and visibility listeners.
395
+ * - Detaches from parent group and clears cached search key.
396
+ * - Completes teardown via `super.destroy()` (base {@link Model} also destroys the view if present).
397
+ *
398
+ * @returns {void}
399
+ * @override
400
+ */
265
401
  public override destroy(): void {
266
402
  if (this.is(LifecycleState.DESTROYED)) {
267
403
  return;