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.
- package/dist/selective-ui.esm.js +4174 -1237
- package/dist/selective-ui.esm.js.map +1 -1
- package/dist/selective-ui.esm.min.js +2 -2
- package/dist/selective-ui.esm.min.js.br +0 -0
- package/dist/selective-ui.min.js +2 -2
- package/dist/selective-ui.min.js.br +0 -0
- package/dist/selective-ui.umd.js +4175 -1238
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/ts/adapter/mixed-adapter.ts +247 -91
- package/src/ts/components/accessorybox.ts +164 -67
- package/src/ts/components/directive.ts +53 -24
- package/src/ts/components/option-handle.ts +121 -54
- package/src/ts/components/placeholder.ts +70 -32
- package/src/ts/components/popup/empty-state.ts +68 -32
- package/src/ts/components/popup/loading-state.ts +70 -30
- package/src/ts/components/popup/popup.ts +0 -1
- package/src/ts/components/searchbox.ts +185 -46
- package/src/ts/components/selectbox.ts +309 -30
- package/src/ts/core/base/adapter.ts +158 -77
- package/src/ts/core/base/fenwick.ts +147 -0
- package/src/ts/core/base/lifecycle.ts +118 -35
- package/src/ts/core/base/model.ts +94 -36
- package/src/ts/core/base/recyclerview.ts +0 -1
- package/src/ts/core/base/view.ts +54 -23
- package/src/ts/core/base/virtual-recyclerview.ts +360 -278
- package/src/ts/core/model-manager.ts +162 -81
- package/src/ts/core/search-controller.ts +164 -91
- package/src/ts/global.ts +1 -1
- package/src/ts/index.ts +1 -1
- package/src/ts/models/group-model.ts +138 -32
- package/src/ts/models/option-model.ts +184 -48
- package/src/ts/services/dataset-observer.ts +72 -10
- package/src/ts/services/ea-observer.ts +87 -10
- package/src/ts/services/effector.ts +181 -32
- package/src/ts/services/refresher.ts +30 -6
- package/src/ts/services/resize-observer.ts +132 -15
- package/src/ts/services/select-observer.ts +115 -50
- package/src/ts/types/utils/ievents.type.ts +6 -1
- package/src/ts/utils/callback-scheduler.ts +112 -34
- package/src/ts/utils/ievents.ts +91 -29
- package/src/ts/utils/selective.ts +330 -61
- package/src/ts/views/group-view.ts +137 -26
- 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
|
-
*
|
|
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
|
-
*
|
|
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 -
|
|
28
|
-
* @param {HTMLOptGroupElement} [targetElement] -
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
105
|
+
* Returns all option values within this group.
|
|
46
106
|
*
|
|
47
|
-
* @
|
|
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
|
|
114
|
+
* Returns the subset of child options that are currently selected.
|
|
55
115
|
*
|
|
56
|
-
* @
|
|
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
|
|
123
|
+
* Returns the subset of child options that are currently visible.
|
|
64
124
|
*
|
|
65
|
-
*
|
|
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
|
-
*
|
|
134
|
+
* Whether the group has at least one visible option.
|
|
73
135
|
*
|
|
74
|
-
* @
|
|
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
|
-
*
|
|
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 -
|
|
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
|
-
*
|
|
93
|
-
*
|
|
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
|
-
*
|
|
202
|
+
* Subscribes to changes in the group's collapsed state.
|
|
118
203
|
*
|
|
119
|
-
*
|
|
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
|
|
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
|
|
232
|
+
* Adds a child option to this group and sets the option's back-reference.
|
|
137
233
|
*
|
|
138
|
-
* @param {OptionModel} optionModel -
|
|
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
|
|
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 -
|
|
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
|
-
*
|
|
160
|
-
*
|
|
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
|
-
*
|
|
30
|
-
* Stores references for later updates and rendering.
|
|
93
|
+
* Creates an option model.
|
|
31
94
|
*
|
|
32
|
-
* @param {SelectiveOptions} options -
|
|
33
|
-
* @param {HTMLOptionElement|null} [targetElement=null] -
|
|
34
|
-
* @param {OptionView|null} [view=null] -
|
|
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(
|
|
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
|
-
*
|
|
124
|
+
* Image source resolved from dataset (`imgsrc` or `image`), or empty string if absent.
|
|
49
125
|
*
|
|
50
|
-
* @
|
|
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
|
-
*
|
|
133
|
+
* Whether this option has an image associated with it.
|
|
58
134
|
*
|
|
59
|
-
* @
|
|
135
|
+
* @returns {boolean}
|
|
60
136
|
*/
|
|
61
137
|
public get hasImage(): boolean {
|
|
62
138
|
return !!this.imageSrc;
|
|
63
139
|
}
|
|
64
140
|
|
|
65
141
|
/**
|
|
66
|
-
*
|
|
142
|
+
* Current value of the backing `<option>`.
|
|
67
143
|
*
|
|
68
|
-
* @
|
|
144
|
+
* @returns {string}
|
|
69
145
|
*/
|
|
70
146
|
public get value(): string {
|
|
71
147
|
return this.targetElement?.value ?? "";
|
|
72
148
|
}
|
|
73
149
|
|
|
74
150
|
/**
|
|
75
|
-
*
|
|
151
|
+
* Whether the backing `<option>` is selected.
|
|
76
152
|
*
|
|
77
|
-
* @
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
175
|
+
* Whether this option is visible (used for filtering/search).
|
|
96
176
|
*
|
|
97
|
-
* @
|
|
177
|
+
* @returns {boolean}
|
|
98
178
|
*/
|
|
99
179
|
public get visible(): boolean {
|
|
100
180
|
return this._visible;
|
|
101
181
|
}
|
|
102
182
|
|
|
103
183
|
/**
|
|
104
|
-
* Sets
|
|
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
|
-
*
|
|
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
|
-
*
|
|
206
|
+
* Reads selected state without emitting external selection listeners.
|
|
120
207
|
*
|
|
121
|
-
* @
|
|
208
|
+
* @returns {boolean}
|
|
122
209
|
*/
|
|
123
210
|
public get selectedNonTrigger(): boolean {
|
|
124
211
|
return this.selected;
|
|
125
212
|
}
|
|
126
213
|
|
|
127
214
|
/**
|
|
128
|
-
* Sets
|
|
129
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
152
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
164
|
-
* stripping HTML if allowHtml is true, otherwise the raw text.
|
|
264
|
+
* Plain-text version of the display label, trimmed.
|
|
165
265
|
*
|
|
166
|
-
* @
|
|
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
|
-
*
|
|
282
|
+
* Dataset object of the backing `<option>` element.
|
|
176
283
|
*
|
|
177
|
-
* @
|
|
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
|
-
*
|
|
291
|
+
* Whether this option is currently highlighted (navigation/hover).
|
|
185
292
|
*
|
|
186
|
-
* @
|
|
293
|
+
* @returns {boolean}
|
|
187
294
|
*/
|
|
188
295
|
public get highlighted(): boolean {
|
|
189
296
|
return this._highlighted;
|
|
190
297
|
}
|
|
191
298
|
|
|
192
299
|
/**
|
|
193
|
-
* Sets
|
|
194
|
-
*
|
|
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
|
-
* @
|
|
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
|
-
*
|
|
317
|
+
* Subscribes to **external** selection changes (emitted by {@link selected}).
|
|
208
318
|
*
|
|
209
|
-
* @param {(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void} callback -
|
|
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
|
-
*
|
|
327
|
+
* Subscribes to **internal** selection changes (emitted by {@link selectedNonTrigger}).
|
|
217
328
|
*
|
|
218
|
-
* @param {(evtToken: IEventCallback, el: OptionModel, selected: boolean) => void} callback -
|
|
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
|
-
*
|
|
337
|
+
* Subscribes to visibility changes (emitted by {@link visible}).
|
|
226
338
|
*
|
|
227
|
-
* @param {(evtToken: IEventCallback, model: OptionModel, visible: boolean) => void} callback -
|
|
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
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
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;
|